Routing
Edit this page on GitHubAt the heart of SvelteKit is a filesystem-based router. The routes of your app — i.e. the URL paths that users can access — are defined by the directories in your codebase:
src/routes
is the root routesrc/routes/about
creates an/about
routesrc/routes/blog/[slug]
creates a route with a parameter,slug
, that can be used to load data dynamically when a user requests a page like/blog/hello-world
You can change
src/routes
to a different directory by editing the project config.
Each route directory contains one or more route files, which can be identified by their +
prefix.
+pagepermalink
+page.sveltepermalink
A +page.svelte
component defines a page of your app. By default, pages are rendered both on the server (SSR) for the initial request and in the browser (CSR) for subsequent navigation.
src/routes/+page.svelte
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
src/routes/about/+page.svelte
<h1>About this site</h1>
<p>TODO...</p>
<a href="/">Home</a>
src/routes/blog/[slug]/+page.svelte
<script>
/** @type {import('./$types').PageData} */ export let data;
</script>
<h1>{data.title}</h1>
<div>{@html data.content}</div>
Note that SvelteKit uses
<a>
elements to navigate between routes, rather than a framework-specific<Link>
component.
+page.jspermalink
Often, a page will need to load some data before it can be rendered. For this, we add a +page.js
(or +page.ts
, if you're TypeScript-inclined) module that exports a load
function:
src/routes/blog/[slug]/+page.js
ts
import {error } from '@sveltejs/kit';/** @type {import('./$types').PageLoad} */export functionload ({params }) {if (params .slug === 'hello-world') {return {title : 'Hello world!',content : 'Welcome to our blog. Lorem ipsum dolor sit amet...'};}throwerror (404, 'Not found');}
This function runs alongside +page.svelte
, which means it runs on the server during server-side rendering and in the browser during client-side navigation. See load
for full details of the API.
As well as load
, page.js
can export values that configure the page's behaviour:
export const prerender = true
orfalse
overridesconfig.kit.prerender.default
export const hydrate = true
orfalse
overridesconfig.kit.browser.hydrate
export const router = true
orfalse
overridesconfig.kit.browser.router
+page.server.jspermalink
If your load
function can only run on the server — for example, if it needs to fetch data from a database or you need to access private environment variables like API keys — then you can rename +page.js
to +page.server.js
and change the PageLoad
type to PageServerLoad
.
src/routes/blog/[slug]/+page.server.js
ts
import {error } from '@sveltejs/kit';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {constpost = awaitgetPostFromDatabase (params .slug );if (post ) {returnpost ;}throwerror (404, 'Not found');}
During client-side navigation, SvelteKit will load this data using fetch
, which means that the returned value must be serializable as JSON.
Actionspermalink
+page.server.js
can also declare actions, which correspond to the POST
, PATCH
, PUT
and DELETE
HTTP methods. A request made to the page with one of these methods will invoke the corresponding action before rendering the page.
An action can return a { status?, errors }
object if there are validation errors (status
defaults to 400
), or an optional { location }
object to redirect the user to another page:
src/routes/login/+page.server.js
ts
import {error } from '@sveltejs/kit';export async functionPOST ({request ,setHeaders ,url }) {constvalues = awaitrequest .formData ();constusername = /** @type {string} */ (values .get ('username'));constpassword = /** @type {string} */ (values .get ('password'));constuser = awaitdb .findUser (username );if (!user ) {return {status : 403,errors : {username : 'No user with this username'}};}if (user .password !==hash (password )) {return {status : 403,errors : {password : 'Incorrect password'}};}setHeaders ({'set-cookie':createSessionCookie (user .id )});return {location :url .searchParams .get ('redirectTo') ?? '/'};}
If validation errors
are returned, they will be available inside +page.svelte
as export let errors
.
The actions API will likely change in the near future: https://github.com/sveltejs/kit/discussions/5875
+errorpermalink
If an error occurs during load
, SvelteKit will render a default error page. You can customise this error page on a per-route basis by adding an +error.svelte
file:
src/routes/blog/[slug]/+error.svelte
<script>
import { page } from '$app/stores';
</script>
<h1>{$page.status}: {$page.error.message}</h1>
SvelteKit will 'walk up the tree' looking for the closest error boundary — if the file above didn't exist it would try src/routes/blog/+error.svelte
and src/routes/+error.svelte
before rendering the default error page.
+layoutpermalink
So far, we've treated pages as entirely standalone components — upon navigation, the existing +page.svelte
component will be destroyed, and a new one will take its place.
But in many apps, there are elements that should be visible on every page, such as top-level navigation or a footer. Instead of repeating them in every +page.svelte
, we can put them in layouts.
+layout.sveltepermalink
To create a layout that applies to every page, make a file called src/routes/+layout.svelte
. The default layout (the one that SvelteKit uses if you don't bring your own) looks like this...
<slot></slot>
...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a <slot>
for the page content. For example, let's add a nav bar:
src/routes/+layout.svelte
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/settings">Settings</a>
</nav>
<slot></slot>
If we create pages for /
, /about
and /settings
...
src/routes/+page.svelte
<h1>Home</h1>
src/routes/about/+page.svelte
<h1>About</h1>
src/routes/settings/+page.svelte
<h1>Settings</h1>
...the nav will always be visible, and clicking between the three pages will only result in the <h1>
being replaced.
Layouts can be nested. Suppose we don't just have a single /settings
page, but instead have nested pages like /settings/profile
and /settings/notifications
with a shared submenu (for a real-life example, see github.com/settings).
We can create a layout that only applies to pages below /settings
(while inheriting the root layout with the top-level nav):
src/routes/settings/+layout.svelte
<script>
/** @type {import('./$types').LayoutData} */ export let data;
</script>
<h1>Settings</h1>
<div class="submenu">
{#each data.sections as section}
<a href="/settings/{section.slug}">{section.title}</a>
{/each}
</div>
<slot></slot>
+layout.jspermalink
Just like +page.svelte
loading data from +page.js
, your +layout.svelte
component can get data from a load
function in +layout.js
.
src/routes/settings/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */export functionload () {return {sections : [{slug : 'profile',title : 'Profile' },{slug : 'notifications',title : 'Notifications' }]};}
Unlike +page.js
, +layout.js
cannot export prerender
, hydrate
and router
, as these are page-level options.
Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently re-run
load
functions when necessary.
+layout.server.jspermalink
To run your layout's load
function on the server, move it to +layout.server.js
, and change the LayoutLoad
type to LayoutServerLoad
.
+serverpermalink
As well as pages, you can define routes with a +server.js
file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your +server.js
file (or +server.ts
) exports functions corresponding to HTTP verbs like GET
, POST
, PATCH
, PUT
and DELETE
that take a RequestEvent
argument and return a Response
object.
For example we could create an /api/random-number
route with a GET
handler:
src/routes/api/random-number/+server.js
ts
import {error } from '@sveltejs/kit';export functionGET ({url }) {constmin =Number (url .searchParams .get ('min') ?? '0');constmax =Number (url .searchParams .get ('max') ?? '1');constd =max -min ;if (isNaN (d ) ||d < 0) {throwerror (400, 'min and max must be numbers, and min must be less than max');}constrandom =min +Math .random () *d ;return newResponse (String (random ));}
The first argument to Response
can be a ReadableStream
, making it possible to stream large amounts of data or create server-sent events (unless deploying to platforms that buffer responses, like AWS Lambda).
$typespermalink
Throughout the examples above, we've been importing types from a $types.d.ts
file. This is a file SvelteKit creates for you in a hidden directory if you're using TypeScript (or JavaScript with JSDoc type annotations) to give you type safety when working with your root files.
For example, annotating export let data
with PageData
(or LayoutData
, for a +layout.svelte
file) tells TypeScript that the type of data
is whatever was returned from load
:
src/routes/blog/[slug]/+page.svelte
<script>
/** @type {import('./$types').PageData} */ export let data;
</script>
In turn, annotating the load
function with PageLoad
, PageServerLoad
, LayoutLoad
or LayoutServerLoad
(for +page.js
, +page.server.js
, +layout.js
and +layout.server.js
respectively) ensures that params
and the return value are correctly typed.
Other filespermalink
Any other files inside a route directory are ignored by SvelteKit. This means you can colocate components and utility modules with the routes that need them.
If components and modules are needed by multiple routes, it's a good idea to put them in $lib
.