diff --git a/.changeset/nine-llamas-fetch.md b/.changeset/nine-llamas-fetch.md new file mode 100644 index 000000000000..3219bf2212e2 --- /dev/null +++ b/.changeset/nine-llamas-fetch.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: skip hooks for server fetch to prerendered routes diff --git a/.changeset/slow-penguins-play.md b/.changeset/slow-penguins-play.md new file mode 100644 index 000000000000..55ea759db7cc --- /dev/null +++ b/.changeset/slow-penguins-play.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: default server fetch to use prerendered paths diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index e02efbdd1e28..855a26300f0e 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -138,6 +138,7 @@ export function create_builder({ generateManifest: ({ relativePath }) => generate_manifest({ build_data, + prerendered: [], relative_path: relativePath, routes: Array.from(filtered) }) @@ -185,6 +186,7 @@ export function create_builder({ generateManifest({ relativePath, routes: subset }) { return generate_manifest({ build_data, + prerendered: prerendered.paths, relative_path: relativePath, routes: subset ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index a2ef4cd4c619..c4f4a5935f4f 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -14,11 +14,12 @@ import { find_server_assets } from './find_server_assets.js'; * build process, to power routing, etc. * @param {{ * build_data: import('types').BuildData; + * prerendered: string[]; * relative_path: string; * routes: import('types').RouteData[]; * }} opts */ -export function generate_manifest({ build_data, relative_path, routes }) { +export function generate_manifest({ build_data, prerendered, relative_path, routes }) { /** * @type {Map} The new index of each node in the filtered nodes array */ @@ -113,6 +114,7 @@ export function generate_manifest({ build_data, relative_path, routes }) { `; }).filter(Boolean).join(',\n')} ], + prerendered_routes: new Set(${s(prerendered)}), matchers: async () => { ${Array.from( matchers, diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 0d5888be0699..192d28b33f45 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1297,6 +1297,7 @@ export interface SSRManifest { client: NonNullable; nodes: SSRNodeLoader[]; routes: SSRRoute[]; + prerendered_routes: Set; matchers: () => Promise>; /** A `[file]: size` map of all assets imported by server code */ server_assets: Record; diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index bca3f84160cb..38d224871933 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -222,6 +222,7 @@ export async function dev(vite, vite_config, svelte_config) { return result; }; }), + prerendered_routes: new Set(), routes: compact( manifest_data.routes.map((route) => { if (!route.page && !route.endpoint) return null; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 82594f0979aa..bd153e2ca7a1 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -795,6 +795,7 @@ Tips: manifest_path, `export const manifest = ${generate_manifest({ build_data, + prerendered: [], relative_path: '.', routes: manifest_data.routes })};\n` @@ -917,6 +918,7 @@ Tips: manifest_path, `export const manifest = ${generate_manifest({ build_data, + prerendered: [], relative_path: '.', routes: manifest_data.routes })};\n` @@ -948,6 +950,7 @@ Tips: `${out}/server/manifest.js`, `export const manifest = ${generate_manifest({ build_data, + prerendered: prerendered.paths, relative_path: '.', routes: manifest_data.routes.filter((route) => prerender_map.get(route.id) !== true) })};\n` diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 417b59c93063..81bd5c665d8e 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -112,6 +112,18 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade return await fetch(request); } + if ( + manifest._.prerendered_routes.has(decoded) || + (decoded.at(-1) === '/' && manifest._.prerendered_routes.has(decoded.slice(0, -1))) + ) { + // The path of something prerendered could match a different route + // that is still in the manifest, leading to the wrong route being loaded. + // We therefore bail early here. The prerendered logic is different for + // each adapter, (except maybe for prerendered redirects) + // so we need to make an actual HTTP request. + return await fetch(request); + } + if (credentials !== 'omit') { const cookie = get_cookie_header(url, request.headers.get('cookie')); if (cookie) { diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js index e485d038d995..1c825a6a6c90 100644 --- a/packages/kit/test/apps/basics/src/hooks.server.js +++ b/packages/kit/test/apps/basics/src/hooks.server.js @@ -1,3 +1,4 @@ +import { building, dev } from '$app/environment'; import { error, isHttpError, redirect } from '@sveltejs/kit'; import { sequence } from '@sveltejs/kit/hooks'; import fs from 'node:fs'; @@ -136,6 +137,15 @@ export const handle = sequence( return resolve(event); }, + async ({ event, resolve }) => { + if (!dev && !building && event.url.pathname === '/prerendering/prerendered-endpoint/api') { + error( + 500, + `Server hooks should not be called for prerendered endpoints: isSubRequest=${event.isSubRequest}` + ); + } + return resolve(event); + }, async ({ event, resolve }) => { if (['/non-existent-route', '/non-existent-route-loop'].includes(event.url.pathname)) { event.locals.url = new URL(event.request.url); diff --git a/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/api-with-param/[option]/+server.js b/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/api-with-param/[option]/+server.js new file mode 100644 index 000000000000..fab18b27ef41 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/api-with-param/[option]/+server.js @@ -0,0 +1,23 @@ +import { building, dev } from '$app/environment'; +import { error, json } from '@sveltejs/kit'; + +export const prerender = 'auto'; + +export function entries() { + return [ + { + option: 'prerendered' + } + ]; +} + +export async function GET({ params: { option } }) { + if ((await entries()).find((entry) => entry.option === option)) { + if (dev || building) { + return json({ message: 'Im prerendered and called from a non-prerendered +page.server.js' }); + } else { + error(500, 'I should not be called at runtime because I am prerendered'); + } + } + return json({ message: 'Im not prerendered' }); +} diff --git a/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/proxy/+server.js b/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/proxy/+server.js index 59396b64d3ad..63cf350239b0 100644 --- a/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/proxy/+server.js +++ b/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/proxy/+server.js @@ -1,3 +1,7 @@ -export async function GET({ fetch }) { +export async function GET({ fetch, url }) { + if (url.searchParams.get('api-with-param-option') === 'prerendered') { + return await fetch('/prerendering/prerendered-endpoint/api-with-param/prerendered'); + } + return await fetch('/prerendering/prerendered-endpoint/api'); } diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index 266373d5069d..e56b8231fb46 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -121,6 +121,27 @@ test.describe('Endpoints', () => { }); }); + test('Partially Prerendered +server.js called from a non-prerendered +server.js works', async ({ + baseURL + }) => { + for (const [description, url] of [ + ['direct', `${baseURL}/prerendering/prerendered-endpoint/api-with-param/prerendered`], + [ + 'proxied', + `${baseURL}/prerendering/prerendered-endpoint/proxy?api-with-param-option=prerendered` + ] + ]) { + await test.step(description, async () => { + const res = await fetch(url); + + expect(res.status).toBe(200); + expect(await res.json()).toStrictEqual({ + message: 'Im prerendered and called from a non-prerendered +page.server.js' + }); + }); + } + }); + test('invalid request method returns allow header', async ({ request }) => { const response = await request.post('/endpoint-output/body'); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index a7b9cf1e4a09..673872faac9d 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1279,6 +1279,7 @@ declare module '@sveltejs/kit' { client: NonNullable; nodes: SSRNodeLoader[]; routes: SSRRoute[]; + prerendered_routes: Set; matchers: () => Promise>; /** A `[file]: size` map of all assets imported by server code */ server_assets: Record;