Skip to content

Commit 67e2342

Browse files
authored
handle non-200 status codes from root __layout.svelte (#4665)
* failing test for #4659 * prevent infinite loop when loading non-existent route in __layout * update test * tighten up * changeset * Update .changeset/purple-rats-wink.md * wut
1 parent 9ad8b6b commit 67e2342

File tree

12 files changed

+56
-9
lines changed

12 files changed

+56
-9
lines changed

.changeset/purple-rats-wink.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
Render generic error page if `__layout` returns error while rendering full error page

packages/kit/src/runtime/load.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export function normalize(loaded) {
2626
const status = loaded.status;
2727

2828
if (!loaded.error && has_error_status) {
29-
return { status: status || 500, error: new Error() };
29+
return {
30+
status: status || 500,
31+
error: new Error(`${status}`)
32+
};
3033
}
3134

3235
const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;

packages/kit/src/runtime/server/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render_page } from './page/index.js';
33
import { render_response } from './page/render.js';
44
import { respond_with_error } from './page/respond_with_error.js';
55
import { coalesce_to_error } from '../../utils/error.js';
6-
import { decode_params, serialize_error } from './utils.js';
6+
import { decode_params, serialize_error, GENERIC_ERROR } from './utils.js';
77
import { normalize_path } from '../../utils/url.js';
88
import { exec } from '../../utils/routing.js';
99
import { negotiate } from '../../utils/http.js';
@@ -274,6 +274,12 @@ export async function respond(request, options, state) {
274274
}
275275
}
276276

277+
if (state.initiator === GENERIC_ERROR) {
278+
return new Response('Internal Server Error', {
279+
status: 500
280+
});
281+
}
282+
277283
// if this request came direct from the user, rather than
278284
// via a `fetch` in a `load`, render a 404 page
279285
if (!state.initiator) {
@@ -328,6 +334,7 @@ export async function respond(request, options, state) {
328334
});
329335
}
330336

337+
// TODO is this necessary? should we just return a plain 500 at this point?
331338
try {
332339
const $session = await options.hooks.getSession(event);
333340
return await respond_with_error({

packages/kit/src/runtime/server/page/load_node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { domain_matches, path_matches } from './cookie.js';
1313
* event: import('types').RequestEvent;
1414
* options: import('types').SSROptions;
1515
* state: import('types').SSRState;
16-
* route: import('types').SSRPage | null;
16+
* route: import('types').SSRPage | import('types').SSRErrorPage;
1717
* node: import('types').SSRNode;
1818
* $session: any;
1919
* stuff: Record<string, any>;

packages/kit/src/runtime/server/page/respond_with_error.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { render_response } from './render.js';
22
import { load_node } from './load_node.js';
33
import { coalesce_to_error } from '../../../utils/error.js';
4+
import { GENERIC_ERROR } from '../utils.js';
45

56
/**
67
* @typedef {import('./types.js').Loaded} Loaded
@@ -41,7 +42,7 @@ export async function respond_with_error({
4142
event,
4243
options,
4344
state,
44-
route: null,
45+
route: GENERIC_ERROR,
4546
node: default_layout,
4647
$session,
4748
stuff: {},
@@ -50,12 +51,16 @@ export async function respond_with_error({
5051
})
5152
);
5253

54+
if (layout_loaded.loaded.error) {
55+
throw layout_loaded.loaded.error;
56+
}
57+
5358
const error_loaded = /** @type {Loaded} */ (
5459
await load_node({
5560
event,
5661
options,
5762
state,
58-
route: null,
63+
route: GENERIC_ERROR,
5964
node: default_error,
6065
$session,
6166
stuff: layout_loaded ? layout_loaded.stuff : {},

packages/kit/src/runtime/server/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,8 @@ function clone_error(error, get_stack) {
9696

9797
return object;
9898
}
99+
100+
/** @type {import('types').SSRErrorPage} */
101+
export const GENERIC_ERROR = {
102+
id: '__error'
103+
};

packages/kit/test/apps/basics/src/routes/__layout.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
<script context="module">
22
/** @type {import('@sveltejs/kit').Load} */
3-
export async function load() {
3+
export async function load({ fetch, url }) {
4+
if (url.pathname.startsWith('/errors/error-in-layout')) {
5+
const res = await fetch('/errors/error-in-layout/non-existent');
6+
7+
return {
8+
status: res.status
9+
};
10+
}
11+
412
return {
513
props: {
614
foo: {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>hello</h1>

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ test.describe('Errors', () => {
317317
await page.goto('/errors/load-status-without-error-client');
318318

319319
expect(await page.textContent('footer')).toBe('Custom layout');
320-
expect(await page.textContent('#message')).toBe('This is your custom error page saying: ""');
320+
expect(await page.textContent('#message')).toBe('This is your custom error page saying: "401"');
321321
expect(await page.innerHTML('h1')).toBe('401');
322322
});
323323
});

packages/kit/test/apps/basics/test/server.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,15 @@ test.describe('Errors', () => {
356356
});
357357
});
358358

359+
test.describe('Load', () => {
360+
test('fetching a non-existent resource in root layout fails without hanging', async ({
361+
request
362+
}) => {
363+
const response = await request.get('/errors/error-in-layout');
364+
expect(await response.text()).toContain('Error: 500');
365+
});
366+
});
367+
359368
test.describe('Routing', () => {
360369
test('event.params are available in handle', async ({ request }) => {
361370
const response = await request.get('/routing/params-in-handle/banana');

0 commit comments

Comments
 (0)