diff --git a/.changeset/witty-falcons-begin.md b/.changeset/witty-falcons-begin.md new file mode 100644 index 000000000000..008233babb17 --- /dev/null +++ b/.changeset/witty-falcons-begin.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[breaking] prerender shells when ssr false and prerender not false diff --git a/packages/kit/src/core/prerender/prerender.js b/packages/kit/src/core/prerender/prerender.js index 9b9d108c13b7..3bcae5076959 100644 --- a/packages/kit/src/core/prerender/prerender.js +++ b/packages/kit/src/core/prerender/prerender.js @@ -401,7 +401,15 @@ export async function prerender() { validate_common_exports(page.universal, route.id); } - const prerender = get_option(nodes, 'prerender') ?? false; + const should_prerender = get_option(nodes, 'prerender'); + const prerender = + should_prerender === true || + // Try prerendering if ssr is false and no server needed. Set it to 'auto' so that + // the route is not removed from the manifest, there could be a server load function. + // People can opt out of this behavior by explicitly setting prerender to false + (should_prerender !== false && get_option(nodes, 'ssr') === false && !page?.server?.actions + ? 'auto' + : false); prerender_map.set(route.id, prerender); } diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 4c3cb438612c..84c712d4fd68 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -79,13 +79,37 @@ export async function render_page(event, route, page, options, state, resolve_op // it's crucial that we do this before returning the non-SSR response, otherwise // SvelteKit will erroneously believe that the path has been prerendered, // causing functions to be omitted from the manifesst generated later - const should_prerender = get_option(nodes, 'prerender') ?? false; + const should_prerender = get_option(nodes, 'prerender'); + if (should_prerender) { const mod = leaf_node.server; if (mod && mod.actions) { throw new Error('Cannot prerender pages with actions'); } } else if (state.prerendering) { + // Try to render the shell when ssr is false and prerendering not explicitly disabled. + // People can opt out of this behavior by explicitly setting prerender to false. + if ( + should_prerender !== false && + get_option(nodes, 'ssr') === false && + !leaf_node.server?.actions + ) { + return await render_response({ + branch: [], + fetched: [], + page_config: { + ssr: false, + csr: get_option(nodes, 'csr') ?? true + }, + status, + error: null, + event, + options, + state, + resolve_opts + }); + } + // if the page isn't marked as prerenderable, then bail out at this point return new Response(undefined, { status: 204 diff --git a/packages/kit/test/prerendering/ssr-false/.gitignore b/packages/kit/test/prerendering/ssr-false/.gitignore new file mode 100644 index 000000000000..1e18f275e97c --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/packages/kit/test/prerendering/ssr-false/package.json b/packages/kit/test/prerendering/ssr-false/package.json new file mode 100644 index 000000000000..0e42986cab3e --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/package.json @@ -0,0 +1,20 @@ +{ + "name": "prerendering-test-ssr-false", + "private": true, + "version": "0.0.2-next.0", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "test": "svelte-kit sync && pnpm build && uvu test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:*", + "svelte": "^3.54.0", + "svelte-check": "^2.9.2", + "typescript": "^4.9.3", + "uvu": "^0.5.6", + "vite": "^4.0.0" + }, + "type": "module" +} diff --git a/packages/kit/test/prerendering/ssr-false/src/app.d.ts b/packages/kit/test/prerendering/ssr-false/src/app.d.ts new file mode 100644 index 000000000000..7b39ff31cd34 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/app.d.ts @@ -0,0 +1 @@ +/// ; diff --git a/packages/kit/test/prerendering/ssr-false/src/app.html b/packages/kit/test/prerendering/ssr-false/src/app.html new file mode 100644 index 000000000000..d35c108bb8f2 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + + %sveltekit.body% + + diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/+layout.js b/packages/kit/test/prerendering/ssr-false/src/routes/+layout.js new file mode 100644 index 000000000000..a3d15781a772 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/+layout.js @@ -0,0 +1 @@ +export const ssr = false; diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/not-prerenderable/+page.server.js b/packages/kit/test/prerendering/ssr-false/src/routes/not-prerenderable/+page.server.js new file mode 100644 index 000000000000..576d6d28bc37 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/not-prerenderable/+page.server.js @@ -0,0 +1 @@ +export const actions = {}; diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/not-prerenderable/+page.svelte b/packages/kit/test/prerendering/ssr-false/src/routes/not-prerenderable/+page.svelte new file mode 100644 index 000000000000..3a4fb4b1663c --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/not-prerenderable/+page.svelte @@ -0,0 +1 @@ +

Not prerenderable because it has +page.server.js actions

diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/opt-out/+page.js b/packages/kit/test/prerendering/ssr-false/src/routes/opt-out/+page.js new file mode 100644 index 000000000000..d43d0cd2a55d --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/opt-out/+page.js @@ -0,0 +1 @@ +export const prerender = false; diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/opt-out/+page.svelte b/packages/kit/test/prerendering/ssr-false/src/routes/opt-out/+page.svelte new file mode 100644 index 000000000000..c2e1feb1d2eb --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/opt-out/+page.svelte @@ -0,0 +1 @@ +

prerenderable shell, but opted out of prerendering explicitly

diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable-2/+page.server.js b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable-2/+page.server.js new file mode 100644 index 000000000000..59b30f2adadf --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable-2/+page.server.js @@ -0,0 +1,3 @@ +export function load() { + throw new Error('I should not be called during prerendering'); +} diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable-2/+page.svelte b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable-2/+page.svelte new file mode 100644 index 000000000000..f844d12b1532 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable-2/+page.svelte @@ -0,0 +1 @@ +

prerenderable shell

diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable/+page.js b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable/+page.js new file mode 100644 index 000000000000..59b30f2adadf --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable/+page.js @@ -0,0 +1,3 @@ +export function load() { + throw new Error('I should not be called during prerendering'); +} diff --git a/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable/+page.svelte b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable/+page.svelte new file mode 100644 index 000000000000..f844d12b1532 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/src/routes/prerenderable/+page.svelte @@ -0,0 +1 @@ +

prerenderable shell

diff --git a/packages/kit/test/prerendering/ssr-false/svelte.config.js b/packages/kit/test/prerendering/ssr-false/svelte.config.js new file mode 100644 index 000000000000..821c14379ec8 --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/svelte.config.js @@ -0,0 +1,6 @@ +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: {} +}; + +export default config; diff --git a/packages/kit/test/prerendering/ssr-false/test/test.js b/packages/kit/test/prerendering/ssr-false/test/test.js new file mode 100644 index 000000000000..6b70952efcce --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/test/test.js @@ -0,0 +1,34 @@ +import * as fs from 'fs'; +import { fileURLToPath } from 'url'; +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; + +const build = fileURLToPath(new URL('../.svelte-kit/output/prerendered/pages', import.meta.url)); + +/** @param {string} file */ +const read = (file, encoding = 'utf-8') => fs.readFileSync(`${build}/${file}`, encoding); + +test('prerenders /prerenderable shell', () => { + const content = read('prerenderable.html'); + assert.ok(!content.includes('prerenderable shell')); +}); + +test('prerenders /prerenderable-2 shell', () => { + const content = read('prerenderable-2.html'); + assert.ok(!content.includes('prerenderable shell')); +}); + +test('does not prerender non prerenderable things', () => { + assert.equal(fs.readdirSync(build).length, 2); +}); + +test('keeps not-explicitly-prerendered routes in the manifest', () => { + const manifest = fileURLToPath( + new URL('../.svelte-kit/output/server/manifest.js', import.meta.url) + ); + const content = fs.readFileSync(manifest, 'utf-8'); + assert.ok(content.includes('/prerenderable')); + assert.ok(content.includes('/prerenderable-2')); +}); + +test.run(); diff --git a/packages/kit/test/prerendering/ssr-false/tsconfig.json b/packages/kit/test/prerendering/ssr-false/tsconfig.json new file mode 100644 index 000000000000..df547741d5bb --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "module": "esnext", + "moduleResolution": "node", + "paths": { + "@sveltejs/kit": ["../../../types"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], + "types": ["../../../types/internal"] + } + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/kit/test/prerendering/ssr-false/vite.config.js b/packages/kit/test/prerendering/ssr-false/vite.config.js new file mode 100644 index 000000000000..fabe48d1677a --- /dev/null +++ b/packages/kit/test/prerendering/ssr-false/vite.config.js @@ -0,0 +1,27 @@ +import * as path from 'path'; +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + + clearScreen: false, + + logLevel: 'silent', + + plugins: [sveltekit()], + + define: { + 'process.env.MY_ENV': '"MY_ENV DEFINED"' + }, + + server: { + fs: { + allow: [path.resolve('../../../src')] + } + } +}; + +export default config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 684b29de747c..b9de9e34f420 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -624,6 +624,22 @@ importers: uvu: 0.5.6 vite: 4.0.0 + packages/kit/test/prerendering/ssr-false: + specifiers: + '@sveltejs/kit': workspace:* + svelte: ^3.54.0 + svelte-check: ^2.9.2 + typescript: ^4.9.3 + uvu: ^0.5.6 + vite: ^4.0.0 + devDependencies: + '@sveltejs/kit': link:../../.. + svelte: 3.54.0 + svelte-check: 2.9.2_svelte@3.54.0 + typescript: 4.9.3 + uvu: 0.5.6 + vite: 4.0.0 + packages/migrate: specifiers: '@types/prompts': ^2.4.1