diff --git a/.changeset/cyan-walls-leave.md b/.changeset/cyan-walls-leave.md
new file mode 100644
index 000000000000..dc01035227d5
--- /dev/null
+++ b/.changeset/cyan-walls-leave.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+feat: allow error boundaries to catch errors on the server
diff --git a/documentation/docs/30-advanced/25-errors.md b/documentation/docs/30-advanced/25-errors.md
index 17d7eabc8268..330f39cbfe50 100644
--- a/documentation/docs/30-advanced/25-errors.md
+++ b/documentation/docs/30-advanced/25-errors.md
@@ -100,6 +100,54 @@ By default, unexpected errors are printed to the console (or, in production, you
Unexpected errors will go through the [`handleError`](hooks#Shared-hooks-handleError) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object which becomes `$page.error`.
+## Rendering errors
+
+Ordinarily, if an error happens during server-side rendering (for example inside a component's `
+
+
{error.message}
+```
+
+The same applies for other error boundaries you define in your code:
+
+```svelte
+
+ ...
+ {#snippet failed(error)}
+
+ {error.message}
+ {/snippet}
+
+```
+
## Responses
If an error occurs inside `handle` or inside a [`+server.js`](routing#server) request handler, SvelteKit will respond with either a fallback error page or a JSON representation of the error object, depending on the request's `Accept` headers.
@@ -127,6 +175,7 @@ If the error instead occurs inside a `load` function while rendering a page, Sve
The exception is when the error occurs inside the root `+layout.js` or `+layout.server.js`, since the root layout would ordinarily _contain_ the `+error.svelte` component. In this case, SvelteKit uses the fallback error page.
+
## Type safety
If you're using TypeScript and need to customize the shape of errors, you can do so by declaring an `App.Error` interface in your app (by convention, in `src/app.d.ts`, though it can live anywhere that TypeScript can 'see'):
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index 14ac3361701b..6b0e9808affc 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -81,7 +81,8 @@ const get_defaults = (prefix = '') => ({
tracing: { server: false },
instrumentation: { server: false },
remoteFunctions: false,
- forkPreloads: false
+ forkPreloads: false,
+ handleRenderingErrors: false
},
files: {
src: join(prefix, 'src'),
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index d16ba4253582..5734e6abea78 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -133,7 +133,8 @@ const options = object(
server: boolean(false)
}),
remoteFunctions: boolean(false),
- forkPreloads: boolean(false)
+ forkPreloads: boolean(false),
+ handleRenderingErrors: boolean(false)
}),
files: object({
diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js
index d8643e989128..d9680e8ab65f 100644
--- a/packages/kit/src/core/sync/sync.js
+++ b/packages/kit/src/core/sync/sync.js
@@ -33,7 +33,7 @@ export function create(config) {
write_client_manifest(config.kit, manifest_data, `${output}/client`);
write_server(config, output);
- write_root(manifest_data, output);
+ write_root(manifest_data, config, output);
write_all_types(config, manifest_data);
write_non_ambient(config.kit, manifest_data);
diff --git a/packages/kit/src/core/sync/write_root.js b/packages/kit/src/core/sync/write_root.js
index 5866b3d8e783..445c17e45dbc 100644
--- a/packages/kit/src/core/sync/write_root.js
+++ b/packages/kit/src/core/sync/write_root.js
@@ -2,11 +2,14 @@ import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';
/**
* @param {import('types').ManifestData} manifest_data
+ * @param {import('types').ValidatedConfig} config
* @param {string} output
*/
-export function write_root(manifest_data, output) {
+export function write_root(manifest_data, config, output) {
// TODO remove default layout altogether
+ const use_boundaries = config.kit.experimental.handleRenderingErrors && isSvelte5Plus();
+
const max_depth = Math.max(
...manifest_data.routes.map((route) =>
route.page ? route.page.layouts.filter(Boolean).length + 1 : 0
@@ -20,8 +23,38 @@ export function write_root(manifest_data, output) {
}
let l = max_depth;
+ /** @type {string} */
+ let pyramid;
- let pyramid = dedent`
+ if (isSvelte5Plus() && use_boundaries) {
+ // with the @const we force the data[depth] access to be derived, which is important to not fire updates needlessly
+ // TODO in Svelte 5 we should rethink the client.js side, we can likely make data a $state and only update indexes that changed there, simplifying this a lot
+ pyramid = dedent`
+ {#snippet pyramid(depth)}
+ {@const Pyramid = constructors[depth]}
+ {#snippet failed(error)}
+ {@const ErrorPage = errors[depth]}
+
+ {/snippet}
+
+ {#if constructors[depth + 1]}
+ {@const d = data[depth]}
+
+
+ {@render pyramid(depth + 1)}
+
+ {:else}
+ {@const d = data[depth]}
+
+
+ {/if}
+
+ {/snippet}
+
+ {@render pyramid(0)}
+ `;
+ } else {
+ pyramid = dedent`
${
isSvelte5Plus()
? `
@@ -29,8 +62,8 @@ export function write_root(manifest_data, output) {
: ``
}`;
- while (l--) {
- pyramid = dedent`
+ while (l--) {
+ pyramid = dedent`
{#if constructors[${l + 1}]}
${
isSvelte5Plus()
@@ -57,6 +90,7 @@ export function write_root(manifest_data, output) {
{/if}
`;
+ }
}
write_if_changed(
@@ -72,9 +106,10 @@ export function write_root(manifest_data, output) {
${
isSvelte5Plus()
? dedent`
- let { stores, page, constructors, components = [], form, ${levels
+ let { stores, page, constructors, components = [], form, ${use_boundaries ? 'errors = [], error, ' : ''}${levels
.map((l) => `data_${l} = null`)
.join(', ')} } = $props();
+ ${use_boundaries ? `let data = $derived({${levels.map((l) => `'${l}': data_${l}`).join(', ')}})` : ''}
`
: dedent`
export let stores;
@@ -108,7 +143,7 @@ export function write_root(manifest_data, output) {
isSvelte5Plus()
? dedent`
$effect(() => {
- stores;page;constructors;components;form;${levels.map((l) => `data_${l}`).join(';')};
+ stores;page;constructors;components;form;${use_boundaries ? 'errors;error;' : ''}${levels.map((l) => `data_${l}`).join(';')};
stores.page.notify();
});
`
diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js
index ea7d9dfe0c99..711a007345e0 100644
--- a/packages/kit/src/core/sync/write_server.js
+++ b/packages/kit/src/core/sync/write_server.js
@@ -50,6 +50,7 @@ export const options = {
root,
service_worker: ${has_service_worker},
service_worker_options: ${config.kit.serviceWorker.register ? s(config.kit.serviceWorker.options) : 'null'},
+ server_error_boundaries: ${s(!!config.kit.experimental.handleRenderingErrors)},
templates: {
app: ({ head, body, assets, nonce, env }) => ${s(template)
.replace('%sveltekit.head%', '" + head + "')
diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts
index c6bc1dcce8df..0e4d5a512b2f 100644
--- a/packages/kit/src/exports/public.d.ts
+++ b/packages/kit/src/exports/public.d.ts
@@ -512,6 +512,15 @@ export interface KitConfig {
* @default false
*/
forkPreloads?: boolean;
+
+ /**
+ * Whether to enable the experimental handling of rendering errors.
+ * When enabled, `` is used to wrap components at each level
+ * where there's an `+error.svelte`, rendering the error page if the component fails.
+ * In addition, error boundaries also work on the server and the error object goes through `handleError`.
+ * @default false
+ */
+ handleRenderingErrors?: boolean;
};
/**
* Where to find various files within your project.
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 95881be7aa86..1d8939798c00 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -367,7 +367,8 @@ async function kit({ svelte_config }) {
__SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative),
__SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
__SVELTEKIT_HASH_ROUTING__: s(kit.router.type === 'hash'),
- __SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server)
+ __SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server),
+ __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: s(kit.experimental.handleRenderingErrors)
};
if (is_build) {
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 0faada3d4b01..2d2631721298 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -238,12 +238,14 @@ const on_navigate_callbacks = new Set();
/** @type {Set<(navigation: import('@sveltejs/kit').AfterNavigate) => void>} */
const after_navigate_callbacks = new Set();
-/** @type {import('./types.js').NavigationState} */
+/** @type {import('./types.js').NavigationState & { nav: import('@sveltejs/kit').NavigationEvent }} */
let current = {
branch: [],
error: null,
// @ts-ignore - we need the initial value to be null
- url: null
+ url: null,
+ // @ts-ignore - we need the initial value to be null
+ nav: null
};
/** this being true means we SSR'd */
@@ -415,7 +417,7 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
navigation_result.props.page.state = prev_state;
}
update(navigation_result.props.page);
- current = navigation_result.state;
+ current = { ...navigation_result.state, nav: current.nav };
reset_invalidation();
root.$set(navigation_result.props);
} else {
@@ -581,7 +583,17 @@ async function _preload_code(url) {
async function initialize(result, target, hydrate) {
if (DEV && result.state.error && document.querySelector('vite-error-overlay')) return;
- current = result.state;
+ /** @type {import('@sveltejs/kit').NavigationEvent} */
+ const nav = {
+ params: current.params,
+ route: { id: current.route?.id ?? null },
+ url: new URL(location.href)
+ };
+
+ current = {
+ ...result.state,
+ nav
+ };
const style = document.querySelector('style[data-sveltekit]');
if (style) style.remove();
@@ -593,7 +605,11 @@ async function initialize(result, target, hydrate) {
props: { ...result.props, stores, components },
hydrate,
// @ts-ignore Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
- sync: false
+ sync: false,
+ // @ts-ignore Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
+ transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
+ ? /** @param {unknown} error */ (error) => handle_error(error, current.nav)
+ : undefined
});
// Wait for a microtask in case svelte experimental async is enabled,
@@ -607,9 +623,7 @@ async function initialize(result, target, hydrate) {
const navigation = {
from: null,
to: {
- params: current.params,
- route: { id: current.route?.id ?? null },
- url: new URL(location.href),
+ ...nav,
scroll: scroll_positions[current_history_index] ?? scroll_state()
},
willUnload: false,
@@ -629,13 +643,23 @@ async function initialize(result, target, hydrate) {
* url: URL;
* params: Record;
* branch: Array;
+ * errors?: Array;
* status: number;
* error: App.Error | null;
* route: import('types').CSRRoute | null;
* form?: Record | null;
* }} opts
*/
-function get_navigation_result_from_branch({ url, params, branch, status, error, route, form }) {
+async function get_navigation_result_from_branch({
+ url,
+ params,
+ branch,
+ errors,
+ status,
+ error,
+ route,
+ form
+}) {
/** @type {import('types').TrailingSlash} */
let slash = 'never';
@@ -670,6 +694,32 @@ function get_navigation_result_from_branch({ url, params, branch, status, error,
}
};
+ if (errors && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
+ let last_idx = -1;
+ result.props.errors = await Promise.all(
+ // eslint-disable-next-line @typescript-eslint/await-thenable
+ branch
+ .map((b, i) => {
+ if (i === 0) return undefined; // root layout wraps root error component, not the other way around
+ if (!b) return null;
+
+ i--;
+ // Find the closest error component up to the previous branch
+ while (i > last_idx + 1 && !errors[i]) i -= 1;
+ last_idx = i;
+ return errors[i]?.()
+ .then((e) => e.component)
+ .catch(() => undefined);
+ })
+ // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
+ .filter((e) => e !== null)
+ );
+ }
+
+ if (error && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
+ result.props.error = error;
+ }
+
if (form !== undefined) {
result.props.form = form;
}
@@ -1201,6 +1251,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
url,
params,
branch: branch.slice(0, error_load.idx).concat(error_load.node),
+ errors,
status,
error,
route
@@ -1220,6 +1271,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
url,
params,
branch,
+ errors,
status: 200,
error: null,
route,
@@ -1325,6 +1377,7 @@ async function load_root_error_page({ status, error, url, route }) {
branch: [root_layout, root_error],
status,
error,
+ errors: [],
route: null
});
} catch (error) {
@@ -1732,7 +1785,16 @@ async function navigate({
});
}
- current = navigation_result.state;
+ // Type-casts are save because we know this resolved a proper SvelteKit route
+ const target = /** @type {import('@sveltejs/kit').NavigationTarget} */ (nav.navigation.to);
+ current = {
+ ...navigation_result.state,
+ nav: {
+ params: /** @type {Record} */ (target.params),
+ route: target.route,
+ url: target.url
+ }
+ };
// reset url before updating page store
if (navigation_result.props.page) {
@@ -2401,16 +2463,17 @@ export async function set_nearest_error_page(error, status = 500) {
const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
if (error_load) {
- const navigation_result = get_navigation_result_from_branch({
+ const navigation_result = await get_navigation_result_from_branch({
url,
params: current.params,
branch: branch.slice(0, error_load.idx).concat(error_load.node),
status,
error,
+ // do not set errors, we haven't changed the page so the previous ones are still current
route
});
- current = navigation_result.state;
+ current = { ...navigation_result.state, nav: current.nav };
root.$set(navigation_result.props);
update(navigation_result.props.page);
@@ -2829,12 +2892,13 @@ async function _hydrate(
}
}
- result = get_navigation_result_from_branch({
+ result = await get_navigation_result_from_branch({
url,
params,
branch,
status,
error,
+ errors: parsed_route?.errors, // TODO load earlier?
form,
route: parsed_route ?? null
});
diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts
index 927b5b7a1041..f91124f962c7 100644
--- a/packages/kit/src/runtime/client/types.d.ts
+++ b/packages/kit/src/runtime/client/types.d.ts
@@ -85,9 +85,11 @@ export type NavigationFinished = {
state: NavigationState;
props: {
constructors: Array;
+ errors?: Array;
components?: SvelteComponent[];
page: Page;
form?: Record | null;
+ error?: App.Error;
[key: `data_${number}`]: Record;
};
};
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index 33afd6ce0ba1..f5bb852a68aa 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -285,6 +285,11 @@ export async function render_page(
const layouts = compact(branch.slice(0, j + 1));
const nodes = new PageNodes(layouts.map((layout) => layout.node));
+ const error_branch = layouts.concat({
+ node,
+ data: null,
+ server_data: null
+ });
return await render_response({
event,
@@ -299,11 +304,14 @@ export async function render_page(
},
status,
error,
- branch: layouts.concat({
- node,
- data: null,
- server_data: null
- }),
+ error_components: await load_error_components(
+ options,
+ ssr,
+ error_branch,
+ page,
+ manifest
+ ),
+ branch: error_branch,
fetched,
data_serializer
});
@@ -350,11 +358,11 @@ export async function render_page(
},
status,
error: null,
- branch: ssr === false ? [] : compact(branch),
+ branch: !ssr ? [] : compact(branch),
action_result,
fetched,
- data_serializer:
- ssr === false ? server_data_serializer(event, event_state, options) : data_serializer
+ data_serializer: !ssr ? server_data_serializer(event, event_state, options) : data_serializer,
+ error_components: await load_error_components(options, ssr, branch, page, manifest)
});
} catch (e) {
// a remote function could have thrown a redirect during render
@@ -376,3 +384,44 @@ export async function render_page(
});
}
}
+
+/**
+ *
+ * @param {import('types').SSROptions} options
+ * @param {boolean} ssr
+ * @param {Array} branch
+ * @param {import('types').PageNodeIndexes} page
+ * @param {import('@sveltejs/kit').SSRManifest} manifest
+ */
+async function load_error_components(options, ssr, branch, page, manifest) {
+ /** @type {Array | undefined} */
+ let error_components;
+
+ if (options.server_error_boundaries && ssr) {
+ let last_idx = -1;
+ error_components = await Promise.all(
+ // eslint-disable-next-line @typescript-eslint/await-thenable
+ branch
+ .map((b, i) => {
+ if (i === 0) return undefined; // root layout wraps root error component, not the other way around
+ if (!b) return null;
+
+ i--;
+ // Find the closest error component up to the previous branch
+ while (i > last_idx + 1 && page.errors[i] === undefined) i -= 1;
+ last_idx = i;
+
+ const idx = page.errors[i];
+ if (idx == null) return undefined;
+
+ return manifest._.nodes[idx]?.()
+ .then((e) => e.component?.())
+ .catch(() => undefined);
+ })
+ // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
+ .filter((e) => e !== null)
+ );
+ }
+
+ return error_components;
+}
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 4a6b80be0373..468a36307851 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -17,6 +17,7 @@ import { try_get_request_store, with_request_store } from '@sveltejs/kit/interna
import { text_encoder } from '../../utils.js';
import { get_global_name } from '../utils.js';
import { create_remote_key } from '../../shared.js';
+import { get_message, get_status } from '../../../utils/error.js';
// TODO rename this function/module
@@ -40,7 +41,8 @@ const updated = {
* event_state: import('types').RequestState;
* resolve_opts: import('types').RequiredResolveOptions;
* action_result?: import('@sveltejs/kit').ActionResult;
- * data_serializer: import('./types.js').ServerDataSerializer
+ * data_serializer: import('./types.js').ServerDataSerializer;
+ * error_components?: Array
* }} opts
*/
export async function render_response({
@@ -56,7 +58,8 @@ export async function render_response({
event_state,
resolve_opts,
action_result,
- data_serializer
+ data_serializer,
+ error_components
}) {
if (state.prerendering) {
if (options.csp.mode === 'nonce') {
@@ -147,6 +150,13 @@ export async function render_response({
form: form_value
};
+ if (error_components) {
+ if (error) {
+ props.error = error;
+ }
+ props.errors = error_components;
+ }
+
let data = {};
// props_n (instead of props[n]) makes it easy to avoid
@@ -176,7 +186,16 @@ export async function render_response({
}
]
]),
- csp: csp.script_needs_nonce ? { nonce: csp.nonce } : { hash: csp.script_needs_hash }
+ csp: csp.script_needs_nonce ? { nonce: csp.nonce } : { hash: csp.script_needs_hash },
+ transformError: error_components
+ ? /** @param {unknown} error */ (error) =>
+ options.hooks.handleError({
+ error,
+ event,
+ status: get_status(error),
+ message: get_message(error)
+ })
+ : undefined
};
const fetch = globalThis.fetch;
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 0767e177d124..71717e209c4b 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -101,6 +101,7 @@ export async function respond_with_error({
status,
error: await handle_error_and_jsonify(event, event_state, options, error),
branch,
+ error_components: [],
fetched,
event,
event_state,
diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts
index 94a63ea2e010..c08d43b3de64 100644
--- a/packages/kit/src/types/global-private.d.ts
+++ b/packages/kit/src/types/global-private.d.ts
@@ -57,6 +57,7 @@ declare global {
* to throw an error if the feature would fail in production.
*/
var __SVELTEKIT_TRACK__: (label: string) => void;
+ var __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: boolean;
var Bun: object;
var Deno: object;
}
diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts
index 3c761b2635f9..7835bf96418c 100644
--- a/packages/kit/src/types/internal.d.ts
+++ b/packages/kit/src/types/internal.d.ts
@@ -468,6 +468,7 @@ export interface SSROptions {
root: SSRComponent['default'];
service_worker: boolean;
service_worker_options: RegistrationOptions;
+ server_error_boundaries: boolean;
templates: {
app(values: {
head: string;
diff --git a/packages/kit/test/apps/async/src/hooks.client.js b/packages/kit/test/apps/async/src/hooks.client.js
new file mode 100644
index 000000000000..af343e2d1022
--- /dev/null
+++ b/packages/kit/test/apps/async/src/hooks.client.js
@@ -0,0 +1,13 @@
+import { isRedirect } from '@sveltejs/kit';
+
+/** @type {import('@sveltejs/kit').HandleClientError} */
+export const handleError = ({ error: e, event, status, message }) => {
+ // helps us catch sveltekit redirects thrown in component code
+ if (isRedirect(e)) {
+ throw new Error("Redirects shouldn't trigger the handleError hook");
+ }
+
+ const error = /** @type {Error} */ (e);
+
+ return { message: `${error.message} (${status} ${message}, on ${event.url.pathname})` };
+};
diff --git a/packages/kit/test/apps/async/src/hooks.server.js b/packages/kit/test/apps/async/src/hooks.server.js
index 943f66f48a40..8cd4c20ccac1 100644
--- a/packages/kit/test/apps/async/src/hooks.server.js
+++ b/packages/kit/test/apps/async/src/hooks.server.js
@@ -6,7 +6,7 @@ export const handleValidationError = ({ issues }) => {
};
/** @type {import('@sveltejs/kit').HandleServerError} */
-export const handleError = ({ error: e, status, message }) => {
+export const handleError = ({ error: e, event, status, message }) => {
// helps us catch sveltekit redirects thrown in component code
if (isRedirect(e)) {
throw new Error("Redirects shouldn't trigger the handleError hook");
@@ -14,7 +14,7 @@ export const handleError = ({ error: e, status, message }) => {
const error = /** @type {Error} */ (e);
- return { message: `${error.message} (${status} ${message})` };
+ return { message: `${error.message} (${status} ${message}, on ${event.url.pathname})` };
};
// @ts-ignore this doesn't exist in old Node TODO remove SvelteKit 3 (same in test-basics)
diff --git a/packages/kit/test/apps/async/src/routes/+error.svelte b/packages/kit/test/apps/async/src/routes/+error.svelte
index 61ad908c0b25..aa74e3a420a2 100644
--- a/packages/kit/test/apps/async/src/routes/+error.svelte
+++ b/packages/kit/test/apps/async/src/routes/+error.svelte
@@ -1,14 +1,17 @@
- Custom error page: {page.error.message}
+ Custom error page: {error.message}
+
{page.status}
-This is your custom error page saying: "{page.error.message}"
+This is your custom error page saying: "{error.message}"