Skip to content

Commit 68599e9

Browse files
authored
fix: allow reroute to point to prerendered route (#13575)
When rerouting to a path that is a prerendered page, the runtime would previously return a 404, because said path does not point to a route in the manifest. This adds a check for this specifically to do a fetch to the prerendered page in that case.
1 parent 8315455 commit 68599e9

File tree

10 files changed

+73
-5
lines changed

10 files changed

+73
-5
lines changed

.changeset/nine-glasses-build.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: allow reroute to point to prerendered route

packages/kit/src/runtime/server/fetch.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as set_cookie_parser from 'set-cookie-parser';
22
import { respond } from './respond.js';
33
import * as paths from '__sveltekit/paths';
44
import { read_implementation } from '__sveltekit/server';
5+
import { has_prerendered_path } from './utils.js';
56

67
/**
78
* @param {{
@@ -112,10 +113,7 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
112113
return await fetch(request);
113114
}
114115

115-
if (
116-
manifest._.prerendered_routes.has(decoded) ||
117-
(decoded.at(-1) === '/' && manifest._.prerendered_routes.has(decoded.slice(0, -1)))
118-
) {
116+
if (has_prerendered_path(manifest, paths.base + decoded)) {
119117
// The path of something prerendered could match a different route
120118
// that is still in the manifest, leading to the wrong route being loaded.
121119
// We therefore bail early here. The prerendered logic is different for

packages/kit/src/runtime/server/respond.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import { render_page } from './page/index.js';
55
import { render_response } from './page/render.js';
66
import { respond_with_error } from './page/respond_with_error.js';
77
import { is_form_content_type } from '../../utils/http.js';
8-
import { handle_fatal_error, method_not_allowed, redirect_response } from './utils.js';
8+
import {
9+
handle_fatal_error,
10+
has_prerendered_path,
11+
method_not_allowed,
12+
redirect_response
13+
} from './utils.js';
914
import { decode_pathname, decode_params, disable_search, normalize_path } from '../../utils/url.js';
1015
import { exec } from '../../utils/routing.js';
1116
import { redirect_json_response, render_data } from './data/index.js';
@@ -21,6 +26,8 @@ import { get_public_env } from './env_module.js';
2126
import { resolve_route } from './page/server_routing.js';
2227
import { validateHeaders } from './validate-headers.js';
2328
import {
29+
add_data_suffix,
30+
add_resolution_suffix,
2431
has_data_suffix,
2532
has_resolution_suffix,
2633
strip_data_suffix,
@@ -191,6 +198,34 @@ export async function respond(request, options, manifest, state) {
191198
return text('Malformed URI', { status: 400 });
192199
}
193200

201+
if (
202+
resolved_path !== url.pathname &&
203+
!state.prerendering?.fallback &&
204+
has_prerendered_path(manifest, resolved_path)
205+
) {
206+
const url = new URL(request.url);
207+
url.pathname = is_data_request
208+
? add_data_suffix(resolved_path)
209+
: is_route_resolution_request
210+
? add_resolution_suffix(resolved_path)
211+
: resolved_path;
212+
213+
// `fetch` automatically decodes the body, so we need to delete the related headers to not break the response
214+
// Also see https://github.com/sveltejs/kit/issues/12197 for more info (we should fix this more generally at some point)
215+
const response = await fetch(url, request);
216+
const headers = new Headers(response.headers);
217+
if (headers.has('content-encoding')) {
218+
headers.delete('content-encoding');
219+
headers.delete('content-length');
220+
}
221+
222+
return new Response(response.body, {
223+
headers,
224+
status: response.status,
225+
statusText: response.statusText
226+
});
227+
}
228+
194229
/** @type {import('types').SSRRoute | null} */
195230
let route = null;
196231

packages/kit/src/runtime/server/utils.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,15 @@ export function stringify_uses(node) {
163163

164164
return `"uses":{${uses.join(',')}}`;
165165
}
166+
167+
/**
168+
* Returns `true` if the given path was prerendered
169+
* @param {import('@sveltejs/kit').SSRManifest} manifest
170+
* @param {string} pathname Should include the base and be decoded
171+
*/
172+
export function has_prerendered_path(manifest, pathname) {
173+
return (
174+
manifest._.prerendered_routes.has(pathname) ||
175+
(pathname.at(-1) === '/' && manifest._.prerendered_routes.has(pathname.slice(0, -1)))
176+
);
177+
}

packages/kit/test/apps/basics/src/hooks.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export const reroute = ({ url, fetch }) => {
3131
return fetch('/reroute/api').then((r) => r.text());
3232
}
3333

34+
if (url.pathname === '/reroute/prerendered/to-destination') {
35+
return '/reroute/prerendered/destination';
36+
}
37+
3438
if (url.pathname in mapping) {
3539
return mapping[url.pathname];
3640
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<a href="/reroute/prerendered/to-destination">to prerendered page</a>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>reroute that points to prerendered page works</h1>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const prerender = true;

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,12 @@ test.describe('reroute', () => {
14611461
);
14621462
});
14631463

1464+
test('Apply reroute to prerendered page during client side navigation', async ({ page }) => {
1465+
await page.goto('/reroute/prerendered');
1466+
await page.click("a[href='/reroute/prerendered/to-destination']");
1467+
expect(await page.textContent('h1')).toContain('reroute that points to prerendered page works');
1468+
});
1469+
14641470
test('Apply reroute after client-only redirects', async ({ page }) => {
14651471
await page.goto('/reroute/client-only-redirect');
14661472
expect(await page.textContent('h1')).toContain('Successfully rewritten');

packages/kit/test/apps/basics/test/server.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,11 @@ test.describe('reroute', () => {
683683
);
684684
});
685685

686+
test('Apply reroute to prerendered page when directly accessing a page', async ({ page }) => {
687+
await page.goto('/reroute/prerendered/to-destination');
688+
expect(await page.textContent('h1')).toContain('reroute that points to prerendered page works');
689+
});
690+
686691
test('Returns a 500 response if reroute throws an error on the server', async ({ page }) => {
687692
const response = await page.goto('/reroute/error-handling/server-error');
688693
expect(response?.status()).toBe(500);

0 commit comments

Comments
 (0)