Skip to content

Commit 858aa8f

Browse files
committed
feat: allow async reroute
closes #13176
1 parent 3cf2b77 commit 858aa8f

File tree

12 files changed

+46
-9
lines changed

12 files changed

+46
-9
lines changed

.changeset/cool-donkeys-pump.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: allow async `reroute`

documentation/docs/30-advanced/20-hooks.md

+2
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ The `lang` parameter will be correctly derived from the returned pathname.
299299

300300
Using `reroute` will _not_ change the contents of the browser's address bar, or the value of `event.url`.
301301

302+
Since version 2.18, the `reroute` hook is allowed to be asynchronous, to for example fetch data from your backend to decide where to reroute. Use this carefully and make sure it's fast, as this will delay navigations otherwise.
303+
302304
### transport
303305

304306
This is a collection of _transporters_, which allow you to pass custom types — returned from `load` and form actions — across the server/client boundary. Each transporter contains an `encode` function, which encodes values on the server (or returns `false` for anything that isn't an instance of the type) and a corresponding `decode` function:

packages/kit/src/exports/public.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ export type ClientInit = () => MaybePromise<void>;
814814
* The [`reroute`](https://svelte.dev/docs/kit/hooks#Universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render.
815815
* @since 2.3.0
816816
*/
817-
export type Reroute = (event: { url: URL }) => void | string;
817+
export type Reroute = (event: { url: URL }) => MaybePromise<void | string>;
818818

819819
/**
820820
* The [`transport`](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport) hook allows you to transport custom types across the server/client boundary.

packages/kit/src/runtime/client/client.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -1198,13 +1198,13 @@ async function load_root_error_page({ status, error, url, route }) {
11981198
/**
11991199
* Resolve the relative rerouted URL for a client-side navigation
12001200
* @param {URL} url
1201-
* @returns {URL | undefined}
1201+
* @returns {Promise<URL | undefined>}
12021202
*/
1203-
function get_rerouted_url(url) {
1204-
// reroute could alter the given URL, so we pass a copy
1203+
async function get_rerouted_url(url) {
12051204
let rerouted;
12061205
try {
1207-
rerouted = app.hooks.reroute({ url: new URL(url) }) ?? url;
1206+
// reroute could alter the given URL, so we pass a copy
1207+
rerouted = (await app.hooks.reroute({ url: new URL(url) })) ?? url;
12081208

12091209
if (typeof rerouted === 'string') {
12101210
const tmp = new URL(url); // do not mutate the incoming URL
@@ -1246,7 +1246,7 @@ async function get_navigation_intent(url, invalidating) {
12461246
if (is_external_url(url, base, app.hash)) return;
12471247

12481248
if (__SVELTEKIT_CLIENT_ROUTING__) {
1249-
const rerouted = get_rerouted_url(url);
1249+
const rerouted = await get_rerouted_url(url);
12501250
if (!rerouted) return;
12511251

12521252
const path = get_url_path(rerouted);
@@ -1997,7 +1997,7 @@ export async function preloadCode(pathname) {
19971997
}
19981998

19991999
if (__SVELTEKIT_CLIENT_ROUTING__) {
2000-
const rerouted = get_rerouted_url(url);
2000+
const rerouted = await get_rerouted_url(url);
20012001
if (!rerouted || !routes.find((route) => route.exec(get_url_path(rerouted)))) {
20022002
throw new Error(`'${pathname}' did not match any routes`);
20032003
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export async function respond(request, options, manifest, state) {
115115

116116
try {
117117
// reroute could alter the given URL, so we pass a copy
118-
resolved_path = options.hooks.reroute({ url: new URL(url) }) ?? url.pathname;
118+
resolved_path = (await options.hooks.reroute({ url: new URL(url) })) ?? url.pathname;
119119
} catch {
120120
return text('Internal Server Error', {
121121
status: 500

packages/kit/test/apps/basics/src/hooks.js

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ export const reroute = ({ url }) => {
2727
throw new Error('Server Error - Should trigger 500 response');
2828
}
2929

30+
if (url.pathname === '/reroute/async/a') {
31+
return new Promise((resolve) => {
32+
setTimeout(() => {
33+
resolve('/reroute/async/b');
34+
}, 100);
35+
});
36+
}
37+
3038
if (url.pathname in mapping) {
3139
return mapping[url.pathname];
3240
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<a href="/reroute/async/a">Go to url that should be rewritten</a>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>Should have been rewritten to <code>/reroute/async/b</code></h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
import { page } from '$app/state';
3+
</script>
4+
5+
<h1>Successfully rewritten, URL should still show a: {page.url.pathname}</h1>

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

+8
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,14 @@ test.describe('reroute', () => {
14021402
);
14031403
});
14041404

1405+
test('Apply async reroute during client side navigation', async ({ page }) => {
1406+
await page.goto('/reroute/async');
1407+
await page.click("a[href='/reroute/async/a']");
1408+
expect(await page.textContent('h1')).toContain(
1409+
'Successfully rewritten, URL should still show a: /reroute/async/a'
1410+
);
1411+
});
1412+
14051413
test('Apply reroute after client-only redirects', async ({ page }) => {
14061414
await page.goto('/reroute/client-only-redirect');
14071415
expect(await page.textContent('h1')).toContain('Successfully rewritten');

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

+7
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,13 @@ test.describe('reroute', () => {
668668
);
669669
});
670670

671+
test('Apply async reroute when directly accessing a page', async ({ page }) => {
672+
await page.goto('/reroute/async/a');
673+
expect(await page.textContent('h1')).toContain(
674+
'Successfully rewritten, URL should still show a: /reroute/async/a'
675+
);
676+
});
677+
671678
test('Returns a 500 response if reroute throws an error on the server', async ({ page }) => {
672679
const response = await page.goto('/reroute/error-handling/server-error');
673680
expect(response?.status()).toBe(500);

packages/kit/types/index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ declare module '@sveltejs/kit' {
796796
* The [`reroute`](https://svelte.dev/docs/kit/hooks#Universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render.
797797
* @since 2.3.0
798798
*/
799-
export type Reroute = (event: { url: URL }) => void | string;
799+
export type Reroute = (event: { url: URL }) => MaybePromise<void | string>;
800800

801801
/**
802802
* The [`transport`](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport) hook allows you to transport custom types across the server/client boundary.

0 commit comments

Comments
 (0)