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 @@
+///
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