diff --git a/.changeset/thin-ears-double.md b/.changeset/thin-ears-double.md new file mode 100644 index 000000000000..ed0e5a8b3975 --- /dev/null +++ b/.changeset/thin-ears-double.md @@ -0,0 +1,5 @@ +--- +"@sveltejs/kit": minor +--- + +feat: add `reroute` hook diff --git a/documentation/docs/30-advanced/20-hooks.md b/documentation/docs/30-advanced/20-hooks.md index d979ec88b1b1..33964bcff3f2 100644 --- a/documentation/docs/30-advanced/20-hooks.md +++ b/documentation/docs/30-advanced/20-hooks.md @@ -4,10 +4,11 @@ title: Hooks 'Hooks' are app-wide functions you declare that SvelteKit will call in response to specific events, giving you fine-grained control over the framework's behaviour. -There are two hooks files, both optional: +There are three hooks files, all optional: - `src/hooks.server.js` — your app's server hooks - `src/hooks.client.js` — your app's client hooks +- `src/hooks.js` — your app's hooks that run on both the client and server Code in these modules will run when the application starts up, making them useful for initializing database clients and so on. @@ -232,6 +233,41 @@ During development, if an error occurs because of a syntax error in your Svelte > Make sure that `handleError` _never_ throws an error +## Universal hooks + +The following can be added to `src/hooks.js`. Universal hooks run on both server and client (not to be confused with shared hooks, which are environment-specific). + +### reroute + +This function allows you to change how URLs are translated into routes. The returned pathname (which defaults to `url.pathname`) is used to select the route and its parameters. + +For example, you might have a `src/routes/[[lang]]/about/+page.svelte` page, which should be accessible as `/en/about` or `/de/ueber-uns` or `/fr/a-propos`. You could implement this with `reroute`: + +```js +/// file: src/hooks.router.js +// @errors: 2345 +// @errors: 2304 + +/** @type {Record} */ +const translated = { + '/en/about': '/en/about', + '/de/ueber-uns': '/de/about', + '/fr/a-propos': '/fr/about', +}; + +/** @type {import('@sveltejs/kit').Reroute} */ +export function reroute({ url }) { + if (url.pathname in translated) { + return translated[url.pathname]; + } +} +``` + +The `lang` parameter will be correctly derived from the returned pathname. + +Using `reroute` will _not_ change the contents of the browser's address bar, or the value of `event.url`. + + ## Further reading - [Tutorial: Hooks](https://learn.svelte.dev/tutorial/handle) diff --git a/packages/kit/src/core/config/index.js b/packages/kit/src/core/config/index.js index cb2a44670bfd..7d4817fce5dc 100644 --- a/packages/kit/src/core/config/index.js +++ b/packages/kit/src/core/config/index.js @@ -93,6 +93,7 @@ function process_config(config, { cwd = process.cwd() } = {}) { if (key === 'hooks') { validated.kit.files.hooks.client = path.resolve(cwd, validated.kit.files.hooks.client); validated.kit.files.hooks.server = path.resolve(cwd, validated.kit.files.hooks.server); + validated.kit.files.hooks.universal = path.resolve(cwd, validated.kit.files.hooks.universal); } else { // @ts-expect-error validated.kit.files[key] = path.resolve(cwd, validated.kit.files[key]); diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index 673515c60802..e5639defc4c2 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -79,7 +79,8 @@ const get_defaults = (prefix = '') => ({ assets: join(prefix, 'static'), hooks: { client: join(prefix, 'src/hooks.client'), - server: join(prefix, 'src/hooks.server') + server: join(prefix, 'src/hooks.server'), + universal: join(prefix, 'src/hooks') }, lib: join(prefix, 'src/lib'), params: join(prefix, 'src/params'), diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index dbbb19d97cce..1d8d4cbc554c 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -123,7 +123,8 @@ const options = object( assets: string('static'), hooks: object({ client: string(join('src', 'hooks.client')), - server: string(join('src', 'hooks.server')) + server: string(join('src', 'hooks.server')), + universal: string(join('src', 'hooks')) }), lib: string(join('src', 'lib')), params: string(join('src', 'params')), diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js index b8b920ffb5f1..1b97ed02e4c1 100644 --- a/packages/kit/src/core/sync/write_client_manifest.js +++ b/packages/kit/src/core/sync/write_client_manifest.js @@ -108,7 +108,8 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { } `; - const hooks_file = resolve_entry(kit.files.hooks.client); + const client_hooks_file = resolve_entry(kit.files.hooks.client); + const universal_hooks_file = resolve_entry(kit.files.hooks.universal); const typo = resolve_entry('src/+hooks.client'); if (typo) { @@ -125,7 +126,16 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { write_if_changed( `${output}/app.js`, dedent` - ${hooks_file ? `import * as client_hooks from '${relative_path(output, hooks_file)}';` : ''} + ${ + client_hooks_file + ? `import * as client_hooks from '${relative_path(output, client_hooks_file)}';` + : '' + } + ${ + universal_hooks_file + ? `import * as universal_hooks from '${relative_path(output, universal_hooks_file)}';` + : '' + } export { matchers } from './matchers.js'; @@ -139,8 +149,10 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { export const hooks = { handleError: ${ - hooks_file ? 'client_hooks.handleError || ' : '' + client_hooks_file ? 'client_hooks.handleError || ' : '' }(({ error }) => { console.error(error) }), + + reroute: ${universal_hooks_file ? 'universal_hooks.reroute || ' : ''}(() => {}) }; export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index c73168962ccc..a781711bbfe9 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -9,7 +9,8 @@ import colors from 'kleur'; /** * @param {{ - * hooks: string | null; + * server_hooks: string | null; + * universal_hooks: string | null; * config: import('types').ValidatedConfig; * has_service_worker: boolean; * runtime_directory: string; @@ -19,7 +20,8 @@ import colors from 'kleur'; */ const server_template = ({ config, - hooks, + server_hooks, + universal_hooks, has_service_worker, runtime_directory, template, @@ -59,8 +61,11 @@ export const options = { version_hash: ${s(hash(config.kit.version.name))} }; -export function get_hooks() { - return ${hooks ? `import(${s(hooks)})` : '{}'}; +export async function get_hooks() { + return { + ${server_hooks ? `...(await import(${s(server_hooks)})),` : ''} + ${universal_hooks ? `...(await import(${s(universal_hooks)})),` : ''} + }; } export { set_assets, set_building, set_prerendering, set_private_env, set_public_env, set_safe_public_env }; @@ -76,7 +81,8 @@ export { set_assets, set_building, set_prerendering, set_private_env, set_public * @param {string} output */ export function write_server(config, output) { - const hooks_file = resolve_entry(config.kit.files.hooks.server); + const server_hooks_file = resolve_entry(config.kit.files.hooks.server); + const universal_hooks_file = resolve_entry(config.kit.files.hooks.universal); const typo = resolve_entry('src/+hooks.server'); if (typo) { @@ -99,7 +105,8 @@ export function write_server(config, output) { `${output}/server/internal.js`, server_template({ config, - hooks: hooks_file ? relative(hooks_file) : null, + server_hooks: server_hooks_file ? relative(server_hooks_file) : null, + universal_hooks: universal_hooks_file ? relative(universal_hooks_file) : null, has_service_worker: config.kit.serviceWorker.register && !!resolve_entry(config.kit.files.serviceWorker), runtime_directory: relative(runtime_directory), diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index c9b1ab6a1f9c..183b85da657c 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -394,6 +394,12 @@ export interface KitConfig { * @default "src/hooks.server" */ server?: string; + /** + * The location of your universal [hooks](https://kit.svelte.dev/docs/hooks). + * @default "src/hooks" + * @since 2.3.0 + */ + universal?: string; }; /** * your app's internal library, accessible throughout the codebase as `$lib` @@ -683,6 +689,12 @@ export type HandleFetch = (input: { fetch: typeof fetch; }) => MaybePromise; +/** + * The [`reroute`](https://kit.svelte.dev/docs/hooks#universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render. + * @since 2.3.0 + */ +export type Reroute = (event: { url: URL }) => void | string; + /** * The generic form of `PageLoad` and `LayoutLoad`. You should import those from `./$types` (see [generated types](https://kit.svelte.dev/docs/types#generated-types)) * rather than using `Load` directly. diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 0cfeb5391b29..283cc0473a30 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1082,13 +1082,31 @@ async function load_root_error_page({ status, error, url, route }) { } /** - * @param {URL} url + * @param {URL | undefined} url * @param {boolean} invalidating */ function get_navigation_intent(url, invalidating) { + if (!url) return undefined; if (is_external_url(url, base)) return; - const path = get_url_path(url.pathname); + // reroute could alter the given URL, so we pass a copy + let rerouted; + try { + rerouted = app.hooks.reroute({ url: new URL(url) }) ?? url.pathname; + } catch (e) { + if (DEV) { + // in development, print the error... + console.error(e); + + // ...and pause execution, since otherwise we will immediately reload the page + debugger; // eslint-disable-line + } + + // fall back to native navigation + return undefined; + } + + const path = get_url_path(rerouted); for (const route of routes) { const params = route.exec(path); @@ -1096,7 +1114,13 @@ function get_navigation_intent(url, invalidating) { if (params) { const id = url.pathname + url.search; /** @type {import('./types.js').NavigationIntent} */ - const intent = { id, invalidating, route, params: decode_params(params), url }; + const intent = { + id, + invalidating, + route, + params: decode_params(params), + url + }; return intent; } } @@ -1462,7 +1486,7 @@ function setup_preload() { if (!options.reload) { if (priority <= options.preload_data) { - const intent = get_navigation_intent(/** @type {URL} */ (url), false); + const intent = get_navigation_intent(url, false); if (intent) { if (DEV) { _preload_data(intent).then((result) => { diff --git a/packages/kit/src/runtime/server/ambient.d.ts b/packages/kit/src/runtime/server/ambient.d.ts index c893c94ff32b..2f38261123dc 100644 --- a/packages/kit/src/runtime/server/ambient.d.ts +++ b/packages/kit/src/runtime/server/ambient.d.ts @@ -1,8 +1,4 @@ declare module '__SERVER__/internal.js' { export const options: import('types').SSROptions; - export const get_hooks: () => Promise<{ - handle?: import('@sveltejs/kit').Handle; - handleError?: import('@sveltejs/kit').HandleServerError; - handleFetch?: import('@sveltejs/kit').HandleFetch; - }>; + export const get_hooks: () => Promise>; } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 37608c28eba8..84257a8c5f36 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -62,7 +62,8 @@ export class Server { this.#options.hooks = { handle: module.handle || (({ event, resolve }) => resolve(event)), handleError: module.handleError || (({ error }) => console.error(error)), - handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)) + handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), + reroute: module.reroute || (() => {}) }; } catch (error) { if (DEV) { @@ -71,7 +72,8 @@ export class Server { throw error; }, handleError: ({ error }) => console.error(error), - handleFetch: ({ request, fetch }) => fetch(request) + handleFetch: ({ request, fetch }) => fetch(request), + reroute: () => {} }; } else { throw error; diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 567097184bf5..7347ef650dba 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -79,9 +79,19 @@ export async function respond(request, options, manifest, state) { } } + // reroute could alter the given URL, so we pass a copy + let rerouted_path; + try { + rerouted_path = options.hooks.reroute({ url: new URL(url) }) ?? url.pathname; + } catch (e) { + return text('Internal Server Error', { + status: 500 + }); + } + let decoded; try { - decoded = decode_pathname(url.pathname); + decoded = decode_pathname(rerouted_path); } catch { return text('Malformed URI', { status: 400 }); } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index a5d91c4c9f59..22f7209be380 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -12,7 +12,8 @@ import { ServerInitOptions, HandleFetch, Actions, - HandleClientError + HandleClientError, + Reroute } from '@sveltejs/kit'; import { HttpMethod, @@ -99,10 +100,12 @@ export interface ServerHooks { handleFetch: HandleFetch; handle: Handle; handleError: HandleServerError; + reroute: Reroute; } export interface ClientHooks { handleError: HandleClientError; + reroute: Reroute; } export interface Env { diff --git a/packages/kit/test/apps/basics/src/hooks.js b/packages/kit/test/apps/basics/src/hooks.js new file mode 100644 index 000000000000..302ab5ec823c --- /dev/null +++ b/packages/kit/test/apps/basics/src/hooks.js @@ -0,0 +1,31 @@ +import { browser } from '$app/environment'; + +const mapping = { + '/reroute/basic/a': '/reroute/basic/b', + '/reroute/client-only-redirect/a': '/reroute/client-only-redirect/b', + '/reroute/preload-data/a': '/reroute/preload-data/b' +}; + +/** @type {import("@sveltejs/kit").Reroute} */ +export const reroute = ({ url }) => { + //Try to rewrite the external url used in /reroute/external to the homepage - This should not work + if (browser && url.href.startsWith('https://expired.badssl.com')) { + return '/'; + } + + if (url.pathname === '/reroute/error-handling/client-error') { + if (browser) { + throw new Error('Client Error'); + } else { + return '/reroute/error-handling/client-error-rewritten'; + } + } + + if (url.pathname === '/reroute/error-handling/server-error') { + throw new Error('Server Error - Should trigger 500 response'); + } + + if (url.pathname in mapping) { + return mapping[url.pathname]; + } +}; diff --git a/packages/kit/test/apps/basics/src/routes/reroute/basic/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/basic/+page.svelte new file mode 100644 index 000000000000..8bd378e3e636 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/basic/+page.svelte @@ -0,0 +1 @@ +Go to url that should be rewritten diff --git a/packages/kit/test/apps/basics/src/routes/reroute/basic/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/basic/a/+page.svelte new file mode 100644 index 000000000000..b7bfd46fcd31 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/basic/a/+page.svelte @@ -0,0 +1 @@ +

Should have been rewritten to /reroute/basic/b

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/basic/b/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/basic/b/+page.svelte new file mode 100644 index 000000000000..540f76eae7b6 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/basic/b/+page.svelte @@ -0,0 +1 @@ +

Successfully rewritten

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/+page.js b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/+page.js new file mode 100644 index 000000000000..d25bd917b5ee --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/+page.js @@ -0,0 +1,10 @@ +import { browser } from '$app/environment'; +import { redirect } from '@sveltejs/kit'; + +export async function load() { + if (browser) { + redirect(302, '/reroute/client-only-redirect/a'); + } + + return {}; +} diff --git a/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/+page.svelte new file mode 100644 index 000000000000..116dcfa97d92 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/+page.svelte @@ -0,0 +1 @@ +

Should be redirected

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/a/+page.svelte new file mode 100644 index 000000000000..0c406d6c7efb --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/a/+page.svelte @@ -0,0 +1 @@ +

Should have been rewritten to /reroute/client-only-redirect/b

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/b/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/b/+page.svelte new file mode 100644 index 000000000000..540f76eae7b6 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/client-only-redirect/b/+page.svelte @@ -0,0 +1 @@ +

Successfully rewritten

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/error-handling/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/+page.svelte new file mode 100644 index 000000000000..f7220e17da8a --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/+page.svelte @@ -0,0 +1,2 @@ +Url with client error +Url with server error diff --git a/packages/kit/test/apps/basics/src/routes/reroute/error-handling/client-error-rewritten/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/client-error-rewritten/+page.svelte new file mode 100644 index 000000000000..b8e8c1634b76 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/client-error-rewritten/+page.svelte @@ -0,0 +1 @@ +

Full Navigation

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/error-handling/client-error/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/client-error/+page.svelte new file mode 100644 index 000000000000..e3d65dd3a0a8 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/client-error/+page.svelte @@ -0,0 +1,4 @@ +

+ The rewrite on this page should fail in the browser, causing a full navigation that resolves to + /reroute/error-handling/client-error-rewritten +

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/error-handling/server-error/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/server-error/+page.svelte new file mode 100644 index 000000000000..3a1cb3796199 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/error-handling/server-error/+page.svelte @@ -0,0 +1 @@ +

Should be unreachable - 500 expected

diff --git a/packages/kit/test/apps/basics/src/routes/reroute/external/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/external/+page.svelte new file mode 100644 index 000000000000..e9bd311a1ecd --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/external/+page.svelte @@ -0,0 +1,2 @@ +Go to rewritten page +External Link diff --git a/packages/kit/test/apps/basics/src/routes/reroute/preload-data/+page.svelte b/packages/kit/test/apps/basics/src/routes/reroute/preload-data/+page.svelte new file mode 100644 index 000000000000..6033f446fdb1 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/preload-data/+page.svelte @@ -0,0 +1,17 @@ + + + + +{#if data} +
{JSON.stringify(data, null, 2)}
+{/if} diff --git a/packages/kit/test/apps/basics/src/routes/reroute/preload-data/a/+page.js b/packages/kit/test/apps/basics/src/routes/reroute/preload-data/a/+page.js new file mode 100644 index 000000000000..81e11ada3b51 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/preload-data/a/+page.js @@ -0,0 +1,5 @@ +export async function load() { + return { + success: false + }; +} diff --git a/packages/kit/test/apps/basics/src/routes/reroute/preload-data/b/+page.js b/packages/kit/test/apps/basics/src/routes/reroute/preload-data/b/+page.js new file mode 100644 index 000000000000..62827c0f1134 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/reroute/preload-data/b/+page.js @@ -0,0 +1,5 @@ +export async function load() { + return { + success: true + }; +} diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 73c4477e709e..40a512a65edb 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -1023,3 +1023,44 @@ test.describe('Shallow routing', () => { await expect(page.locator('p')).toHaveText('active: true'); }); }); + +test.describe('reroute', () => { + test('Apply reroute during client side navigation', async ({ page }) => { + await page.goto('/reroute/basic'); + await page.click("a[href='/reroute/basic/a']"); + expect(await page.textContent('h1')).toContain('Successfully rewritten'); + }); + + test('Apply reroute after client-only redirects', async ({ page }) => { + await page.goto('/reroute/client-only-redirect'); + expect(await page.textContent('h1')).toContain('Successfully rewritten'); + }); + + test('Apply reroute to preload data', async ({ page }) => { + await page.goto('/reroute/preload-data'); + await page.click('button'); + await page.waitForSelector('pre'); + expect(await page.textContent('pre')).toContain('"success": true'); + }); + + test('reroute does not get applied to external URLs', async ({ page }) => { + await page.goto('/reroute/external'); + const current_url = new URL(page.url()); + + //click the link with the text External URL + await page.click("a[data-test='external-url']"); + + //The URl should not have the same origin as the current URL + const new_url = new URL(page.url()); + expect(current_url.origin).not.toEqual(new_url.origin); + }); + + test('Falls back to native navigation if reroute throws on the client', async ({ page }) => { + await page.goto('/reroute/error-handling'); + + //click the link with the text External URL + await page.click('a#client-error'); + + expect(await page.textContent('h1')).toContain('Full Navigation'); + }); +}); diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index 8f3fe6377bf9..78c408aeda8f 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -610,3 +610,15 @@ test.describe('Miscellaneous', () => { expect(await response.text()).toBe('foo'); }); }); + +test.describe('reroute', () => { + test('Apply reroute when directly accessing a page', async ({ page }) => { + await page.goto('/reroute/basic/a'); + expect(await page.textContent('h1')).toContain('Successfully rewritten'); + }); + + test('Returns a 500 response if reroute throws an error on the server', async ({ page }) => { + const response = await page.goto('/reroute/error-handling/server-error'); + expect(response?.status()).toBe(500); + }); +}); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c92970267010..f9945914e31e 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -376,6 +376,12 @@ declare module '@sveltejs/kit' { * @default "src/hooks.server" */ server?: string; + /** + * The location of your universal [hooks](https://kit.svelte.dev/docs/hooks). + * @default "src/hooks" + * @since 2.3.0 + */ + universal?: string; }; /** * your app's internal library, accessible throughout the codebase as `$lib` @@ -665,6 +671,12 @@ declare module '@sveltejs/kit' { fetch: typeof fetch; }) => MaybePromise; + /** + * The [`reroute`](https://kit.svelte.dev/docs/hooks#universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render. + * @since 2.3.0 + */ + export type Reroute = (event: { url: URL }) => void | string; + /** * The generic form of `PageLoad` and `LayoutLoad`. You should import those from `./$types` (see [generated types](https://kit.svelte.dev/docs/types#generated-types)) * rather than using `Load` directly.