Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spicy-planes-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

chore: share the nearest error page walk between client and server
45 changes: 19 additions & 26 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { read_ndjson } from './ndjson.js';
import Root from '../components/root.svelte';
import { Props, RenderNode } from '../props.svelte.js';
import { init_transport, parse, stringify } from '#app/internal/transport';
import { build_error_chain } from '../error-chain.js';
import { build_error_chain, nearest_error_pages } from '../error-chain.js';

/**
* @typedef {{
Expand Down Expand Up @@ -1643,27 +1643,20 @@ async function load_route({ id, invalidating, url, params, route, preload, actio
* @param {number} i Start index to backtrack from
* @param {Array<import('./types.js').BranchNode | undefined>} branch Branch to backtrack
* @param {Array<import('types').CSRPageNodeLoader | undefined>} errors All error pages for this branch
* @returns {Promise<{idx: number; node: import('./types.js').BranchNode} | undefined>}
* @returns {Promise<Array<import('./types.js').BranchNode | undefined> | undefined>} the branch truncated at the error page's depth
*/
async function load_nearest_error_page(i, branch, errors) {
while (i--) {
if (errors[i]) {
let j = i;
while (!branch[j]) j -= 1;
try {
return {
idx: j + 1,
node: {
node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
data: {},
server: null,
universal: null
}
};
} catch {
continue;
}
for (const { error, idx } of nearest_error_pages(i, branch, errors)) {
try {
return branch.slice(0, idx).concat({
node: await error(),
loader: error,
data: {},
server: null,
universal: null
});
} catch {
continue;
}
}
}
Expand All @@ -1681,12 +1674,12 @@ async function load_nearest_error_page(i, branch, errors) {
* }} opts
*/
async function load_route_error({ i, branch, errors, error, status, url, params, route }) {
const error_load = await load_nearest_error_page(i, branch, errors);
if (error_load) {
const error_branch = await load_nearest_error_page(i, branch, errors);
if (error_branch) {
return get_navigation_result_from_branch({
url,
params,
branch: branch.slice(0, error_load.idx).concat(error_load.node),
branch: error_branch,
errors,
error,
status,
Expand Down Expand Up @@ -3198,12 +3191,12 @@ export async function set_nearest_error_page(error) {
const { branch, route } = current;
if (!route) return;

const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
if (error_load) {
const error_branch = await load_nearest_error_page(current.branch.length, branch, route.errors);
if (error_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),
branch: error_branch,
error,
errors: route.errors,
route
Expand Down
21 changes: 21 additions & 0 deletions packages/kit/src/runtime/error-chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,24 @@ export function build_error_chain(branch, errors, load) {
// eslint-disable-next-line @typescript-eslint/await-thenable
return Promise.all(chain);
}

/**
* Walks up from the node at index `i` through the `+error.svelte` pages declared strictly
* above it, nearest first. Yields each candidate with the branch depth it attaches at,
* rewound past empty branch slots, so callers can skip candidates that fail to load.
* @template T
* @param {number} i
* @param {Array<unknown>} branch
* @param {Array<T | undefined | null>} errors the error page declared at each depth, if any
* @returns {Generator<{ error: T; idx: number }>}
*/
export function* nearest_error_pages(i, branch, errors) {
while (i--) {
const error = errors[i];
if (error != null) {
let j = i;
while (!branch[j]) j -= 1;
yield { error, idx: j + 1 };
}
}
}
68 changes: 31 additions & 37 deletions packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { compact } from '../../../utils/array.js';
import { get_status, normalize_error } from '../../../utils/error.js';
import { noop } from '../../../utils/functions.js';
import { add_data_suffix } from '../../pathname.js';
import { build_error_chain } from '../../error-chain.js';
import { build_error_chain, nearest_error_pages } from '../../error-chain.js';
import { redirect_response } from '../utils.js';
import { static_error_page, handle_error_and_jsonify } from '../errors.js';
import {
Expand Down Expand Up @@ -278,42 +278,36 @@ export async function render_page(event, state, page, options, manifest, nodes,
const error = await handle_error_and_jsonify(event, state, options, err);
const status = error.status;

while (i--) {
if (page.errors[i]) {
const index = /** @type {number} */ (page.errors[i]);
const node = await manifest._.nodes[index]();

let j = i;
while (!branch[j]) j -= 1;

data_serializer.set_max_nodes(j + 1);

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,
state,
options,
manifest,
resolve_opts,
page_config: {
ssr: nodes.ssr(),
csr: nodes.csr()
},
status,
error,
error_components: await load_error_components(ssr, error_branch, page, manifest),
branch: error_branch,
fetched,
data_serializer
});
}
for (const { error: index, idx } of nearest_error_pages(i, branch, page.errors)) {
const node = await manifest._.nodes[index]();

data_serializer.set_max_nodes(idx);

const layouts = compact(branch.slice(0, idx));
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,
state,
options,
manifest,
resolve_opts,
page_config: {
ssr: nodes.ssr(),
csr: nodes.csr()
},
status,
error,
error_components: await load_error_components(ssr, error_branch, page, manifest),
branch: error_branch,
fetched,
data_serializer
});
}

// if we're still here, it means the error happened in the root layout,
Expand Down
Loading