Skip to content
Draft
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/modern-dodos-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': major
---

feat: add error result type to `preloadData`
33 changes: 26 additions & 7 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -989,10 +989,14 @@ function diff_search_params(old_url, new_url) {
}

/**
* @param {Omit<import('./types.js').NavigationFinished['state'], 'branch'> & { error: App.Error }} opts
* @param {Omit<import('./types.js').NavigationFinished['state'], 'branch'> & { error: App.Error; status: number }} opts
* @returns {import('./types.js').NavigationFinished}
*/
function preload_error({ error, url, route, params }) {
function preload_error({ error, status, url, route, params }) {
// we skipped loading the error page, so we need to use the current page
// store, but we still pass the updated status to the preloadData function
const new_page = clone_page(page);
new_page.status = status;
return {
type: 'loaded',
state: {
Expand All @@ -1003,7 +1007,7 @@ function preload_error({ error, url, route, params }) {
branch: []
},
props: {
page: clone_page(page),
page: new_page,
constructors: []
}
};
Expand Down Expand Up @@ -1068,12 +1072,14 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
} catch (error) {
const handled_error = await handle_error(error, { url, params, route: { id } });

const status = get_status(error);

if (preload_tokens.has(preload)) {
return preload_error({ error: handled_error, url, params, route });
return preload_error({ error: handled_error, status, url, params, route });
}

return load_root_error_page({
status: get_status(error),
status,
error: handled_error,
url,
route
Expand Down Expand Up @@ -1154,20 +1160,23 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
if (err instanceof Redirect) {
return {
type: 'redirect',
status: err.status,
location: err.location
};
}

let status = get_status(err);

if (preload_tokens.has(preload)) {
return preload_error({
error: await handle_error(err, { params, url, route: { id: route.id } }),
status,
url,
params,
route
});
}

let status = get_status(err);
/** @type {App.Error} */
let error;

Expand Down Expand Up @@ -2185,7 +2194,7 @@ export function refreshAll({ includeLoadFunctions = true } = {}) {
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
*
* @param {string} href Page to preload
* @returns {Promise<{ type: 'loaded'; status: number; data: Record<string, any> } | { type: 'redirect'; location: string }>}
* @returns {Promise<({ type: 'loaded'; data: Record<string, any> } | { type: 'redirect'; location: string } | { type: 'error'; error: App.Error }) & { status: number; }>}
*/
export async function preloadData(href) {
if (!BROWSER) {
Expand All @@ -2203,11 +2212,21 @@ export async function preloadData(href) {
if (result.type === 'redirect') {
return {
type: result.type,
status: result.status,
location: result.location
};
}

const { status, data } = result.props.page ?? page;

if (result.type === 'loaded' && result.state.error) {
return {
type: 'error',
status,
error: result.state.error
};
}

return { type: result.type, status, data };
}

Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export type NavigationResult = NavigationRedirect | NavigationFinished;

export type NavigationRedirect = {
type: 'redirect';
status: number;
location: string;
};

Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/server/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export function redirect_json_response(redirect) {
return json_response(
/** @type {import('types').ServerRedirectNode} */ ({
type: 'redirect',
status: redirect.status,
location: redirect.location
})
);
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export async function render_page(
if (state.prerendering && should_prerender_data) {
const body = JSON.stringify({
type: 'redirect',
status: err.status,
location: err.location
});

Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export interface RouteData {

export type ServerRedirectNode = {
type: 'redirect';
status: number;
location: string;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script>
import { preloadData } from '$app/navigation';

let data = {};
</script>

<button
type="button"
on:click={async () => {
data = await preloadData('/routing/preloading/error/404');
}}>404</button
>

<button
type="button"
on:click={async () => {
data = await preloadData('/routing/preloading/error/500');
}}>500</button
>

<p>{data.type} {data.status} {data.error?.message}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { error } from '@sveltejs/kit';

export function load({ params }) {
if (params.id === '404') {
error(404, { message: 'Not found' });
}

if (params.id === '500') {
throw new Error('Oopsie');
}
}
12 changes: 12 additions & 0 deletions packages/kit/test/apps/basics/test/cross-platform/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,18 @@ test.describe('Prefetching', () => {
await app.goto('/routing/preloading/preload-error');
await expect(page.locator('p')).toHaveText('hello');
});

test('preloads errors', async ({ page }) => {
await page.goto('/routing/preloading/error');

await expect(page.locator('p')).toHaveText('undefined undefined undefined');

page.locator('button', { hasText: '404' }).click();
await expect(page.locator('p')).toHaveText('error 404 Not found');

page.locator('button', { hasText: '500' }).click();
await expect(page.locator('p')).toHaveText('error 500 Oopsie (500 Internal Error)');
});
});

test.describe('Routing', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/kit/test/prerendering/basics/test/tests.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ test('renders a server-side redirect', () => {

expect(data).toEqual({
type: 'redirect',
status: 301,
location: 'https://example.com/redirected'
});
});
Expand Down
8 changes: 6 additions & 2 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3056,13 +3056,17 @@ declare module '$app/navigation' {
*
* @param href Page to preload
* */
export function preloadData(href: string): Promise<{
export function preloadData(href: string): Promise<({
type: "loaded";
status: number;
data: Record<string, any>;
} | {
type: "redirect";
location: string;
} | {
type: "error";
error: App.Error;
}) & {
status: number;
}>;
/**
* Programmatically imports the code for routes that haven't yet been fetched.
Expand Down
Loading