Skip to main content

Advanced routing

Edit this page on GitHub

Rest parameterspermalink

If the number of route segments is unknown, you can use rest syntax — for example you might implement GitHub's file viewer like so...

/[org]/[repo]/tree/[branch]/[...file]

...in which case a request for /sveltejs/kit/tree/master/documentation/docs/04-advanced-routing.md would result in the following parameters being available to the page:

ts
{
org: 'sveltejs',
repo: 'kit',
branch: 'master',
file: 'documentation/docs/04-advanced-routing.md'
}

src/routes/a/[...rest]/z/+page.svelte will match /a/z (i.e. there's no parameter at all) as well as /a/b/z and /a/b/c/z and so on. Make sure you check that the value of the rest parameter is valid, for example using a matcher.

404 pagespermalink

Rest parameters also allow you to render custom 404s. Given these routes...

src/routes/
├ marx-brothers/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte

...the marx-brothers/+error.svelte file will not be rendered if you visit /marx-brothers/karl, because no route was matched. If you want to render the nested error page, you should create a route that matches any /marx-brothers/* request, and return a 404 from it:

src/routes/
├ marx-brothers/
| ├ [...path]/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
src/routes/marx-brothers/[...path]/+page.js
ts
import { error } from '@sveltejs/kit';
 
/** @type {import('./$types').PageLoad} */
export function load(event) {
throw error(404, 'Not Found');
}
src/routes/marx-brothers/[...path]/+page.ts
ts
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';
 
export const load: PageLoad = (event) => {
throw error(404, 'Not Found');
}

If you don't handle 404 cases, they will appear in handleError

Matchingpermalink

A route like src/routes/archive/[page] would match /archive/3, but it would also match /archive/potato. We don't want that. You can ensure that route parameters are well-formed by adding a matcher — which takes the parameter string ("3" or "potato") and returns true if it is valid — to your params directory...

src/params/integer.js
ts
/** @type {import('@sveltejs/kit').ParamMatcher} */
export function match(param) {
return /^\d+$/.test(param);
}
src/params/integer.ts
ts
import type { ParamMatcher } from '@sveltejs/kit';
 
export const match: ParamMatcher = (param) => {
return /^\d+$/.test(param);
}

...and augmenting your routes:

src/routes/archive/[page]
src/routes/archive/[page=integer]

If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404.

Matchers run both on the server and in the browser.

Sortingpermalink

It's possible for multiple routes to match a given path. For example each of these routes would match /foo-abc:

src/routes/[...catchall]/+page.svelte
src/routes/[a]/+server.js
src/routes/[b]/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/foo-abc/+page.svelte

SvelteKit needs to know which route is being requested. To do so, it sorts them according to the following rules...

  • More specific routes are higher priority (e.g. a route with no parameters is more specific than a route with one dynamic parameter, and so on)
  • +server files have higher priority than +page files
  • Parameters with matchers ([name=type]) are higher priority than those without ([name])
  • Rest parameters have lowest priority
  • Ties are resolved alphabetically

...resulting in this ordering, meaning that /foo-abc will invoke src/routes/foo-abc/+page.svelte, and /foo-def will invoke src/routes/foo-[c]/+page.svelte rather than less specific routes:

src/routes/foo-abc/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/[a]/+server.js
src/routes/[b]/+page.svelte
src/routes/[...catchall]/+page.svelte

Encodingpermalink

Directory names are URI-decoded, meaning that (for example) a directory like %40[username] would match characters beginning with @:

ts
assert.equal(
decodeURIComponent('%40[username]'),
'@[username]'
);

To express a % character, use %25, otherwise the result will be malformed.

Advanced layoutspermalink

By default, the layout hierarchy mirrors the route hierarchy. In some cases, that might not be what you want.

(group)permalink

Perhaps you have some routes that are 'app' routes that should have one layout (e.g. /dashboard or /item), and others that are 'marketing' routes that should have a different layout (/blog or /testimonials). We can group these routes with a directory whose name is wrapped in parentheses — unlike normal directories, (app) and (marketing) do not affect the URL pathname of the routes inside them:

src/routes/
│ (app)/
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
│ (marketing)/
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte

You can also put a +page directly inside a (group), for example if / should be an (app) or a (marketing) page.

Pages and layouts inside groups — as in any other directory — will inherit layouts above them, unless they break out of the layout hierarchy as shown in the next section. In the above example, (app)/+layout.svelte and (marketing)/+layout.svelte both inherit +layout.svelte.

+page@permalink

Conversely, some routes of your app might need to break out of the layout hierarchy. Let's add an /item/[id]/embed route inside the (app) group from the previous example:

src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte

Ordinarily, this would inherit the root layout, the (app) layout, the item layout and the [id] layout. We can reset to one of those layouts by appending @ followed by the segment name — or, for the root layout, the empty string. In this example, we can choose from the following options:

  • +page@[id].svelte - inherits from src/routes/(app)/item/[id]/+layout.svelte
  • +page@item.svelte - inherits from src/routes/(app)/item/+layout.svelte
  • +page@(app).svelte - inherits from src/routes/(app)/+layout.svelte
  • +page@.svelte - inherits from src/routes/+layout.svelte
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page@(app).svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte

There is no way to break out of the root layout. You can be sure that it's always present in your app and for example put app-wide UI or behavior in it.

+layout@permalink

Like pages, layouts can themselves break out of their parent layout hierarchy, using the same technique. For example, a +layout@.svelte component would reset the hierarchy for all its child routes.

src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte  // uses (app)/item/[id]/+layout.svelte
│ │ │ └ +layout.svelte  // inherits from (app)/item/+layout@.svelte
│ │ │ └ +page.svelte    // uses (app)/item/+layout@.svelte
│ │ └ +layout@.svelte   // inherits from root layout, skipping (app)/+layout.svelte
│ └ +layout.svelte
└ +layout.svelte

When to use layout groupspermalink

Not all use cases are suited for layout grouping, nor should you feel compelled to use them. It might be that your use case would result in complex (group) nesting, or that you don't want to introduce a (group) for a single outlier. It's perfectly fine to use other means such as composition (reusable load functions or Svelte components) or if-statements to achieve what you want. The following example shows a layout that rewinds to the root layout and reuses components and functions that other layouts can also use:

src/routes/nested/route/+layout@.svelte
<script>
  import ReusableLayout from '$lib/ReusableLayout.svelte';
  export let data;
</script>

<ReusableLayout {data}>
  <slot />
</ReusableLayout>
src/routes/nested/route/+layout.js
ts
import { reusableLoad } from '$lib/reusable-load-function';
 
/** @type {import('./$types').PageLoad} */
export function load(event) {
// Add additional logic here, if needed
return reusableLoad(event);
}
src/routes/nested/route/+layout.ts
ts
import { reusableLoad } from '$lib/reusable-load-function';
import type { PageLoad } from './$types';
 
export const load: PageLoad = (event) => {
// Add additional logic here, if needed
return reusableLoad(event);
}
previous Routing
We stand with Ukraine. Donate → We stand with Ukraine. Petition your leaders. Show your support.