diff --git a/documentation/docs/11-ssr-and-javascript.md b/documentation/docs/11-ssr-and-javascript.md
index fe9b3e0721a7c..b0675a0d844bb 100644
--- a/documentation/docs/11-ssr-and-javascript.md
+++ b/documentation/docs/11-ssr-and-javascript.md
@@ -6,15 +6,17 @@ By default, SvelteKit will render any component first on the server and send it
You can control each of these on a per-app or per-page basis. Note that each of the per-page settings use [`context="module"`](https://svelte.dev/docs#script_context_module), and only apply to page components, _not_ [layout](#layouts) components.
-If both are specified, per-page settings override per-app settings in case of conflicts. Each setting can be controlled independently, but `ssr` and `hydrate` cannot both be `false` since that would result in nothing being rendered at all.
+The app-wide config options take a function, which lets you set configure the option in an advanced manner on a per-request and per-page basis. E.g. you could disable SSR for `/admin` or enable SSR only for search engine crawlers (aka [dynamic rendering](https://developers.google.com/search/docs/advanced/javascript/dynamic-rendering)).
+
+ Each setting can be controlled independently, but `ssr` and `hydrate` cannot both be `false` since that would result in nothing being rendered at all.
### ssr
-Disabling [server-side rendering](#appendix-ssr) effectively turns your SvelteKit app into a [**single-page app** or SPA](#appendix-csr-and-spa).
+Disabling [server-side rendering](#appendix-ssr) effectively turns your SvelteKit app into a [**single-page app** or SPA](#appendix-csr-and-spa). The default app-wide config option value is a function which reads the page value. Reading the page value causes the page to be loaded on the server. If you'd like to avoid this because you're building a SPA, you will need to set a value such as a boolean for each of the four rendering options which does not access the page-level settings.
> In most situations this is not recommended: see [the discussion in the appendix](#appendix-ssr). Consider whether it's truly appropriate to disable and don't simply disable SSR because you've hit an issue with it.
-You can disable SSR app-wide with the [`ssr` config option](#configuration-ssr), or a page-level `ssr` export:
+SSR can be configured with app-wide [`ssr` config option](#configuration-ssr), or a page-level `ssr` export:
```html
```
diff --git a/packages/kit/src/core/adapt/prerender.js b/packages/kit/src/core/adapt/prerender.js
index 9721c91db0fa3..8110da75b23ef 100644
--- a/packages/kit/src/core/adapt/prerender.js
+++ b/packages/kit/src/core/adapt/prerender.js
@@ -76,13 +76,14 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a
});
/** @type {(status: number, path: string, parent: string | null, verb: string) => void} */
- const error = config.kit.prerender.force
- ? (status, path, parent, verb) => {
- log.error(`${status} ${path}${parent ? ` (${verb} from ${parent})` : ''}`);
- }
- : (status, path, parent, verb) => {
- throw new Error(`${status} ${path}${parent ? ` (${verb} from ${parent})` : ''}`);
- };
+ const error =
+ config.kit.prerender.enabled === true
+ ? (status, path, parent, verb) => {
+ log.error(`${status} ${path}${parent ? ` (${verb} from ${parent})` : ''}`);
+ }
+ : (status, path, parent, verb) => {
+ throw new Error(`${status} ${path}${parent ? ` (${verb} from ${parent})` : ''}`);
+ };
const files = new Set([...build_data.static, ...build_data.client]);
diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js
index b960d3f14cdfa..0c90fb1cfd6cc 100644
--- a/packages/kit/src/core/build/index.js
+++ b/packages/kit/src/core/build/index.js
@@ -312,8 +312,9 @@ async function build_server(
set_paths(settings.paths);
set_prerendering(settings.prerendering || false);
+ const config = settings.config;
options = {
- amp: ${config.kit.amp},
+ amp: config.kit.amp,
dev: false,
entry: {
file: ${s(prefix + client_manifest[client_entry_file].file)},
@@ -321,7 +322,7 @@ async function build_server(
js: ${s(Array.from(entry_js).map(dep => prefix + dep))}
},
fetched: undefined,
- floc: ${config.kit.floc},
+ floc: config.kit.floc,
get_component_path: id => ${s(`${config.kit.paths.assets}/${config.kit.appDir}/`)} + entry_lookup[id],
get_stack: error => String(error), // for security
handle_error: /** @param {Error & {frame?: string}} error */ (error) => {
@@ -332,19 +333,20 @@ async function build_server(
error.stack = options.get_stack(error);
},
hooks: get_hooks(user_hooks),
- hydrate: ${s(config.kit.hydrate)},
+ hydrate: config.kit.hydrate,
initiator: undefined,
load_component,
manifest,
paths: settings.paths,
+ prerender: config.kit.prerender && config.kit.prerender.enabled,
read: settings.read,
root,
service_worker: ${service_worker_entry_file ? "'/service-worker.js'" : 'null'},
- router: ${s(config.kit.router)},
- ssr: ${s(config.kit.ssr)},
- target: ${s(config.kit.target)},
+ router: config.kit.router,
+ ssr: config.kit.ssr,
+ target: config.kit.target,
template,
- trailing_slash: ${s(config.kit.trailingSlash)}
+ trailing_slash: config.kit.trailingSlash
};
}
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index f0954248b95d2..4432d455fe36b 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -4,9 +4,7 @@ import { deep_merge, validate_config } from './index.js';
test('fills in defaults', () => {
const validated = validate_config({});
-
- // @ts-expect-error
- delete validated.kit.vite;
+ delete_complex_opts(validated);
assert.equal(validated, {
compilerOptions: null,
@@ -27,7 +25,6 @@ test('fills in defaults', () => {
floc: false,
host: null,
hostHeader: null,
- hydrate: true,
package: {
dir: 'package',
exports: {
@@ -49,12 +46,8 @@ test('fills in defaults', () => {
},
prerender: {
crawl: true,
- enabled: true,
- force: false,
pages: ['*']
},
- router: true,
- ssr: true,
target: null,
trailingSlash: 'never'
},
@@ -105,8 +98,7 @@ test('fills in partial blanks', () => {
assert.equal(validated.kit.vite(), {});
- // @ts-expect-error
- delete validated.kit.vite;
+ delete_complex_opts(validated);
assert.equal(validated, {
compilerOptions: null,
@@ -127,7 +119,6 @@ test('fills in partial blanks', () => {
floc: false,
host: null,
hostHeader: null,
- hydrate: true,
package: {
dir: 'package',
exports: {
@@ -149,12 +140,8 @@ test('fills in partial blanks', () => {
},
prerender: {
crawl: true,
- enabled: true,
- force: false,
pages: ['*']
},
- router: true,
- ssr: true,
target: null,
trailingSlash: 'never'
},
@@ -513,3 +500,17 @@ deepMergeSuite('merge including toString', () => {
});
deepMergeSuite.run();
+
+/** @param {import('types/config').ValidatedConfig} validated */
+function delete_complex_opts(validated) {
+ // @ts-expect-error
+ delete validated.kit.vite;
+ // @ts-expect-error
+ delete validated.kit.hydrate;
+ // @ts-expect-error
+ delete validated.kit.prerender.enabled;
+ // @ts-expect-error
+ delete validated.kit.router;
+ // @ts-expect-error
+ delete validated.kit.ssr;
+}
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index 633c3ad75c843..a1eea6393c4b6 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -78,7 +78,10 @@ const options = {
hostHeader: expect_string(null),
- hydrate: expect_boolean(true),
+ hydrate: expect_page_scriptable(async ({ page }) => {
+ const leaf = await page;
+ return 'hydrate' in leaf ? !!leaf.hydrate : true;
+ }),
serviceWorker: {
type: 'branch',
children: {
@@ -120,8 +123,10 @@ const options = {
type: 'branch',
children: {
crawl: expect_boolean(true),
- enabled: expect_boolean(true),
- force: expect_boolean(false),
+ enabled: expect_page_scriptable(async ({ page }) => {
+ const leaf = await page;
+ return 'prerender' in leaf ? !!leaf.prerender : true;
+ }),
pages: {
type: 'leaf',
default: ['*'],
@@ -144,9 +149,15 @@ const options = {
}
},
- router: expect_boolean(true),
+ router: expect_page_scriptable(async ({ page }) => {
+ const leaf = await page;
+ return 'router' in leaf ? !!leaf.router : true;
+ }),
- ssr: expect_boolean(true),
+ ssr: expect_page_scriptable(async ({ page }) => {
+ const leaf = await page;
+ return 'ssr' in leaf ? !!leaf.ssr : true;
+ }),
target: expect_string(null),
@@ -233,6 +244,23 @@ function expect_boolean(boolean) {
};
}
+/**
+ * @param {import('types/config').ScriptablePageOpt} value
+ * @returns {ConfigDefinition}
+ */
+function expect_page_scriptable(value) {
+ return {
+ type: 'leaf',
+ default: value,
+ validate: (option, keypath) => {
+ if (typeof option !== 'boolean' && typeof option !== 'function') {
+ throw new Error(`${keypath} should be a boolean or function that returns one`);
+ }
+ return option;
+ }
+ };
+}
+
/**
* @param {string[]} options
* @returns {ConfigDefinition}
diff --git a/packages/kit/src/core/config/test/index.js b/packages/kit/src/core/config/test/index.js
index daa1c8b400b75..a3d3cc0cf2c24 100644
--- a/packages/kit/src/core/config/test/index.js
+++ b/packages/kit/src/core/config/test/index.js
@@ -14,9 +14,7 @@ async function testLoadDefaultConfig(path) {
const cwd = join(__dirname, 'fixtures', path);
const config = await load_config({ cwd });
-
- // @ts-expect-error
- delete config.kit.vite; // can't test equality of a function
+ delete_complex_opts(config);
assert.equal(config, {
compilerOptions: null,
@@ -37,7 +35,6 @@ async function testLoadDefaultConfig(path) {
floc: false,
host: null,
hostHeader: null,
- hydrate: true,
package: {
dir: 'package',
exports: {
@@ -54,9 +51,7 @@ async function testLoadDefaultConfig(path) {
exclude: []
},
paths: { base: '', assets: '/.' },
- prerender: { crawl: true, enabled: true, force: false, pages: ['*'] },
- router: true,
- ssr: true,
+ prerender: { crawl: true, pages: ['*'] },
target: null,
trailingSlash: 'never'
},
@@ -103,3 +98,17 @@ test('errors on loading config with incorrect default export', async () => {
});
test.run();
+
+/** @param {import('types/config').ValidatedConfig} validated */
+function delete_complex_opts(validated) {
+ // @ts-expect-error
+ delete validated.kit.vite;
+ // @ts-expect-error
+ delete validated.kit.hydrate;
+ // @ts-expect-error
+ delete validated.kit.prerender.enabled;
+ // @ts-expect-error
+ delete validated.kit.router;
+ // @ts-expect-error
+ delete validated.kit.ssr;
+}
diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js
index 039c040e134bc..7949a32f2d0bc 100644
--- a/packages/kit/src/core/dev/index.js
+++ b/packages/kit/src/core/dev/index.js
@@ -392,6 +392,7 @@ async function create_handler(vite, config, dir, cwd, manifest) {
};
},
manifest,
+ prerender: config.kit.prerender.enabled,
read: (file) => fs.readFileSync(path.join(config.kit.files.assets, file)),
root,
router: config.kit.router,
diff --git a/packages/kit/src/core/preview/index.js b/packages/kit/src/core/preview/index.js
index 2d671e6ca106c..73202f1afaedd 100644
--- a/packages/kit/src/core/preview/index.js
+++ b/packages/kit/src/core/preview/index.js
@@ -45,6 +45,7 @@ export async function preview({ port, host, config, https: use_https = false, cw
});
app.init({
+ config,
paths: {
base: '',
assets: '/.'
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 24c8cbb477964..f785475ce3060 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -48,7 +48,7 @@ export async function respond(incoming, options, state = {}) {
return await render_response({
options,
$session: await options.hooks.getSession(request),
- page_config: { ssr: false, router: true, hydrate: true },
+ page_config: { ssr: false, router: true, hydrate: true, prerender: true },
status: 200,
branch: []
});
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 492d302398fcb..7ecf620e155d5 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -10,7 +10,7 @@ const s = JSON.stringify;
* @param {{
* options: import('types/internal').SSRRenderOptions;
* $session: any;
- * page_config: { hydrate: boolean, router: boolean, ssr: boolean };
+ * page_config: import('types/config').PageOpts;
* status: number;
* error?: Error,
* branch?: Array;
diff --git a/packages/kit/src/runtime/server/page/respond.js b/packages/kit/src/runtime/server/page/respond.js
index 59a2a361c6a22..1fe77799644e6 100644
--- a/packages/kit/src/runtime/server/page/respond.js
+++ b/packages/kit/src/runtime/server/page/respond.js
@@ -1,7 +1,7 @@
import { render_response } from './render.js';
import { load_node } from './load_node.js';
import { respond_with_error } from './respond_with_error.js';
-import { coalesce_to_error } from '../utils.js';
+import { coalesce_to_error, resolve_option } from '../utils.js';
/** @typedef {import('./types.js').Loaded} Loaded */
@@ -27,36 +27,19 @@ export async function respond({ request, options, state, $session, route }) {
params
};
- let nodes;
-
- try {
- nodes = await Promise.all(route.a.map((id) => options.load_component(id)));
- } catch (/** @type {unknown} */ err) {
- const error = coalesce_to_error(err);
-
- options.handle_error(error);
-
- return await respond_with_error({
- request,
- options,
- state,
- $session,
- status: 500,
- error
- });
- }
-
- const leaf = nodes[nodes.length - 1].module;
+ const leaf_promise = async () => {
+ return (await options.load_component(route.a[route.a.length - 1])).module;
+ };
const page_config = {
- ssr: 'ssr' in leaf ? !!leaf.ssr : options.ssr,
- router: 'router' in leaf ? !!leaf.router : options.router,
- hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate
+ ssr: resolve_option(options.ssr, { request, page: leaf_promise() }),
+ router: resolve_option(options.router, { request, page: leaf_promise() }),
+ hydrate: resolve_option(options.hydrate, { request, page: leaf_promise() }),
+ prerender: resolve_option(options.prerender, { request, page: leaf_promise() })
};
- if (!leaf.prerender && state.prerender && !state.prerender.all) {
- // if the page has `export const prerender = true`, continue,
- // otherwise bail out at this point
+ // if prerendering some pages, but not this one
+ if (state.prerender && !state.prerender.all && !page_config.prerender) {
return {
status: 204,
headers: {},
@@ -74,6 +57,29 @@ export async function respond({ request, options, state, $session, route }) {
let error;
ssr: if (page_config.ssr) {
+ /**
+ * The layout components and page components for a page
+ * @type {import('types/internal').SSRNode[]}
+ */
+ let nodes;
+
+ try {
+ nodes = await Promise.all(route.a.map((id) => options.load_component(id)));
+ } catch (/** @type {unknown} */ err) {
+ const error = coalesce_to_error(err);
+
+ options.handle_error(error);
+
+ return await respond_with_error({
+ request,
+ options,
+ state,
+ $session,
+ status: 500,
+ error
+ });
+ }
+
let context = {};
branch = [];
diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js
index 1bdaa0e201174..c5f84ff05a8f6 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -1,6 +1,6 @@
import { render_response } from './render.js';
import { load_node } from './load_node.js';
-import { coalesce_to_error } from '../utils.js';
+import { coalesce_to_error, resolve_option } from '../utils.js';
/**
* @param {{
@@ -55,15 +55,20 @@ export async function respond_with_error({ request, options, state, $session, st
}))
];
+ const leaf_promise = async () => branch[branch.length - 1].node.module;
+
+ const page_config = {
+ ssr: resolve_option(options.ssr, { request, page: leaf_promise() }),
+ router: resolve_option(options.router, { request, page: leaf_promise() }),
+ hydrate: resolve_option(options.hydrate, { request, page: leaf_promise() }),
+ prerender: resolve_option(options.prerender, { request, page: leaf_promise() })
+ };
+
try {
return await render_response({
options,
$session,
- page_config: {
- hydrate: options.hydrate,
- router: options.router,
- ssr: options.ssr
- },
+ page_config,
status,
error,
branch,
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index 74fde8c68a70c..dfd11f1263c28 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -17,3 +17,15 @@ export function lowercase_keys(obj) {
export function coalesce_to_error(err) {
return err instanceof Error ? err : new Error(JSON.stringify(err));
}
+
+/**
+ * @param {any} opt
+ * @param {object} ctx
+ * @returns
+ */
+export function resolve_option(opt, ctx) {
+ if (typeof opt === 'function') {
+ return opt(ctx);
+ }
+ return opt;
+}
diff --git a/packages/kit/test/test.js b/packages/kit/test/test.js
index 12781f4af3fd8..0ea6d6a45fd24 100644
--- a/packages/kit/test/test.js
+++ b/packages/kit/test/test.js
@@ -170,7 +170,6 @@ function duplicate(test_fn, config, is_build) {
if (process.env.FILTER && !name.includes(process.env.FILTER)) return;
test_fn(name, async (context) => {
-
let response;
if (start) {
@@ -194,7 +193,6 @@ function duplicate(test_fn, config, is_build) {
if (process.env.FILTER && !name.includes(process.env.FILTER)) return;
test_fn(name, async (context) => {
-
let response;
if (start) {
diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts
index 45482dddb12fe..2907aaff4fd38 100644
--- a/packages/kit/types/config.d.ts
+++ b/packages/kit/types/config.d.ts
@@ -1,7 +1,8 @@
+import { ServerRequest } from './hooks';
import { Logger, TrailingSlash } from './internal';
import { UserConfig as ViteConfig } from 'vite';
-export type AdapterUtils = {
+export interface AdapterUtils {
log: Logger;
rimraf: (dir: string) => void;
mkdirp: (dir: string) => void;
@@ -18,14 +19,28 @@ export type AdapterUtils = {
dest: string;
fallback?: string;
}) => Promise;
-};
+}
-export type Adapter = {
+export interface Adapter {
name: string;
adapt: ({ utils, config }: { utils: AdapterUtils; config: ValidatedConfig }) => Promise;
-};
+}
-export type Config = {
+export interface PageOpts {
+ ssr: boolean;
+ router: boolean;
+ hydrate: boolean;
+ prerender: boolean;
+}
+
+export interface PageOptsContext {
+ request: ServerRequest;
+ page: Promise;
+}
+
+export type ScriptablePageOpt = T | (({ request, page }: PageOptsContext) => Promise);
+
+export interface Config {
compilerOptions?: any;
extensions?: string[];
kit?: {
@@ -43,7 +58,7 @@ export type Config = {
floc?: boolean;
host?: string;
hostHeader?: string;
- hydrate?: boolean;
+ hydrate?: ScriptablePageOpt;
package?: {
dir?: string;
emitTypes?: boolean;
@@ -62,23 +77,22 @@ export type Config = {
};
prerender?: {
crawl?: boolean;
- enabled?: boolean;
- force?: boolean;
+ enabled?: ScriptablePageOpt;
pages?: string[];
};
- router?: boolean;
+ router?: ScriptablePageOpt;
serviceWorker?: {
exclude?: string[];
};
- ssr?: boolean;
+ ssr?: ScriptablePageOpt;
target?: string;
trailingSlash?: TrailingSlash;
vite?: ViteConfig | (() => ViteConfig);
};
preprocess?: any;
-};
+}
-export type ValidatedConfig = {
+export interface ValidatedConfig {
compilerOptions: any;
extensions: string[];
kit: {
@@ -97,7 +111,7 @@ export type ValidatedConfig = {
floc: boolean;
host: string;
hostHeader: string;
- hydrate: boolean;
+ hydrate: ScriptablePageOpt;
package: {
dir: string;
emitTypes: boolean;
@@ -116,18 +130,17 @@ export type ValidatedConfig = {
};
prerender: {
crawl: boolean;
- enabled: boolean;
- force: boolean;
+ enabled: ScriptablePageOpt;
pages: string[];
};
- router: boolean;
+ router: ScriptablePageOpt;
serviceWorker: {
exclude: string[];
};
- ssr: boolean;
+ ssr: ScriptablePageOpt;
target: string;
trailingSlash: TrailingSlash;
vite: () => ViteConfig;
};
preprocess: any;
-};
+}
diff --git a/packages/kit/types/endpoint.d.ts b/packages/kit/types/endpoint.d.ts
index 2687562aebf67..1b4df7f6aed5e 100644
--- a/packages/kit/types/endpoint.d.ts
+++ b/packages/kit/types/endpoint.d.ts
@@ -12,11 +12,11 @@ type JSONValue =
type DefaultBody = JSONValue | Uint8Array;
-export type EndpointOutput = {
+export interface EndpointOutput {
status?: number;
headers?: Headers;
body?: Body;
-};
+}
export type RequestHandler<
Locals = Record,
diff --git a/packages/kit/types/hooks.d.ts b/packages/kit/types/hooks.d.ts
index d50776a133f76..c80787f58a2d3 100644
--- a/packages/kit/types/hooks.d.ts
+++ b/packages/kit/types/hooks.d.ts
@@ -2,23 +2,23 @@ import { Headers, Location, MaybePromise, ParameterizedBody } from './helper';
export type StrictBody = string | Uint8Array;
-export type ServerRequest, Body = unknown> = Location & {
+export interface ServerRequest, Body = unknown> extends Location {
method: string;
headers: Headers;
rawBody: StrictBody;
body: ParameterizedBody;
locals: Locals;
-};
+}
-export type ServerResponse = {
+export interface ServerResponse {
status: number;
headers: Headers;
body?: StrictBody;
-};
+}
-export type GetSession, Session = any> = {
+export interface GetSession, Session = any> {
(request: ServerRequest): MaybePromise;
-};
+}
export type Handle> = (input: {
request: ServerRequest;
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index 599c481ae1fcf..24abb74e3a62c 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -1,3 +1,4 @@
+import { PageOpts, ScriptablePageOpt, ValidatedConfig } from './config';
import { RequestHandler } from './endpoint';
import { Headers, Location, ParameterizedBody } from './helper';
import { GetSession, Handle, ServerResponse, ServerFetch, StrictBody } from './hooks';
@@ -23,10 +24,12 @@ export type Logger = {
export type App = {
init: ({
+ config,
paths,
prerendering,
read
}: {
+ config: ValidatedConfig;
paths: {
base: string;
assets: string;
@@ -46,11 +49,7 @@ export type App = {
) => Promise;
};
-export type SSRComponent = {
- ssr?: boolean;
- router?: boolean;
- hydrate?: boolean;
- prerender?: boolean;
+export interface SSRComponent extends PageOpts {
preload?: any; // TODO remove for 1.0
load: Load;
default: {
@@ -65,7 +64,7 @@ export type SSRComponent = {
};
};
};
-};
+}
export type SSRComponentLoader = () => Promise;
@@ -142,18 +141,19 @@ export type SSRRenderOptions = {
get_stack: (error: Error) => string | undefined;
handle_error: (error: Error) => void;
hooks: Hooks;
- hydrate: boolean;
+ hydrate: ScriptablePageOpt;
load_component: (id: PageId) => Promise;
manifest: SSRManifest;
paths: {
base: string;
assets: string;
};
+ prerender: ScriptablePageOpt;
read: (file: string) => Buffer;
root: SSRComponent['default'];
- router: boolean;
+ router: ScriptablePageOpt;
service_worker?: string;
- ssr: boolean;
+ ssr: ScriptablePageOpt;
target: string;
template: ({ head, body }: { head: string; body: string }) => string;
trailing_slash: TrailingSlash;