/ routing.md
routing.md
1 At 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: 2 3 - `src/routes` is the root route 4 - `src/routes/about` creates an `/about` route 5 - `src/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` 6 7 > [!NOTE] You can change `src/routes` to a different directory by editing the [project config](configuration). 8 9 Each route directory contains one or more _route files_, which can be identified by their `+` prefix. 10 11 We'll introduce these files in a moment in more detail, but here are a few simple rules to help you remember how SvelteKit's routing works: 12 13 - All files can run on the server 14 - All files run on the client except `+server` files 15 - `+layout` and `+error` files apply to subdirectories as well as the directory they live in 16 17 ## +page 18 19 ### +page.svelte 20 21 A `+page.svelte` component defines a page of your app. By default, pages are rendered both on the server ([SSR](glossary#SSR)) for the initial request and in the browser ([CSR](glossary#CSR)) for subsequent navigation. 22 23 ```svelte 24 <!--- file: src/routes/+page.svelte ---> 25 <h1>Hello and welcome to my site!</h1> 26 <a href="/about">About my site</a> 27 ``` 28 29 ```svelte 30 <!--- file: src/routes/about/+page.svelte ---> 31 <h1>About this site</h1> 32 <p>TODO...</p> 33 <a href="/">Home</a> 34 ``` 35 36 > [!NOTE] SvelteKit uses `<a>` elements to navigate between routes, rather than a framework-specific `<Link>` component. 37 38 Pages can receive data from `load` functions via the `data` prop. 39 40 ```svelte 41 <!--- file: src/routes/blog/[slug]/+page.svelte ---> 42 <script> 43 /** @type {import('./$types').PageProps} */ 44 let { data } = $props(); 45 </script> 46 47 <h1>{data.title}</h1><div>{@html data.content}</div> 48 ``` 49 50 As of 2.24, pages also receive a `params` prop which is typed based on the route parameters. This is particularly useful alongside [remote functions](remote-functions): 51 52 ```svelte 53 <!--- file: src/routes/blog/[slug]/+page.svelte ---> 54 <script> 55 import { getPost } from '../blog.remote'; 56 57 /** @type {import('./$types').PageProps} */ 58 let { params } = $props(); 59 60 const post = $derived(await getPost(params.slug)); 61 </script> 62 63 <h1>{post.title}</h1><div>{@html post.content}</div> 64 ``` 65 66 > [!LEGACY] 67 > `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` property manually with `PageData` instead, see [$types](#$types). 68 > 69 > In Svelte 4, you'd use `export let data` instead. 70 71 ### +page.js 72 73 Often, a page will need to load some data before it can be rendered. For this, we add a `+page.js` module that exports a `load` function: 74 75 ```js 76 /// file: src/routes/blog/[slug]/+page.js 77 import { error } from '@sveltejs/kit'; 78 79 /** @type {import('./$types').PageLoad} */ 80 export function load({ params }) { 81 if (params.slug === 'hello-world') { 82 return { 83 title: 'Hello world!', 84 content: 'Welcome to our blog. Lorem ipsum dolor sit amet...' 85 }; 86 } 87 88 error(404, 'Not found'); 89 } 90 ``` 91 92 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`](load) for full details of the API. 93 94 As well as `load`, `+page.js` can export values that configure the page's behaviour: 95 96 - `export const prerender = true` or `false` or `'auto'` 97 - `export const ssr = true` or `false` 98 - `export const csr = true` or `false` 99 100 You can find more information about these in [page options](page-options). 101 102 ### +page.server.js 103 104 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]($env-static-private) like API keys — then you can rename `+page.js` to `+page.server.js` and change the `PageLoad` type to `PageServerLoad`. 105 106 ```js 107 /// file: src/routes/blog/[slug]/+page.server.js 108 109 // @filename: ambient.d.ts 110 declare global { 111 const getPostFromDatabase: (slug: string) => { 112 title: string; 113 content: string; 114 } 115 } 116 117 export {}; 118 119 // @filename: index.js 120 // ---cut--- 121 import { error } from '@sveltejs/kit'; 122 123 /** @type {import('./$types').PageServerLoad} */ 124 export async function load({ params }) { 125 const post = await getPostFromDatabase(params.slug); 126 127 if (post) { 128 return post; 129 } 130 131 error(404, 'Not found'); 132 } 133 ``` 134 135 During client-side navigation, SvelteKit will load this data from the server, which means that the returned value must be serializable using [devalue](https://github.com/rich-harris/devalue). See [`load`](load) for full details of the API. 136 137 Like `+page.js`, `+page.server.js` can export [page options](page-options) — `prerender`, `ssr` and `csr`. 138 139 A `+page.server.js` file can also export _actions_. If `load` lets you read data from the server, `actions` let you write data _to_ the server using the `<form>` element. To learn how to use them, see the [form actions](form-actions) section. 140 141 ## +error 142 143 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: 144 145 ```svelte 146 <!--- file: src/routes/blog/[slug]/+error.svelte ---> 147 <script> 148 import { page } from '$app/state'; 149 </script> 150 151 <h1>{page.status}: {page.error.message}</h1> 152 ``` 153 154 > [!LEGACY] 155 > `$app/state` was added in SvelteKit 2.12. If you're using an earlier version or are using Svelte 4, use `$app/stores` instead. 156 157 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 then `src/routes/+error.svelte` before rendering the default error page. If _that_ fails (or if the error was thrown from the `load` function of the root `+layout`, which sits 'above' the root `+error`), SvelteKit will bail out and render a static fallback error page, which you can customise by creating a `src/error.html` file. 158 159 If the error occurs inside a `load` function in `+layout(.server).js`, the closest error boundary in the tree is an `+error.svelte` file _above_ that layout (not next to it). 160 161 If no route can be found (404), `src/routes/+error.svelte` (or the default error page, if that file does not exist) will be used. 162 163 > [!NOTE] `+error.svelte` is _not_ used when an error occurs inside [`handle`](hooks#Server-hooks-handle) or a [+server.js](#server) request handler. 164 165 You can read more about error handling [here](errors). 166 167 ## +layout 168 169 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. 170 171 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_. 172 173 ### +layout.svelte 174 175 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... 176 177 ```svelte 178 <script> 179 let { children } = $props(); 180 </script> 181 182 {@render children()} 183 ``` 184 185 ...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a `@render` tag for the page content. For example, let's add a nav bar: 186 187 ```svelte 188 <!--- file: src/routes/+layout.svelte ---> 189 <script> 190 let { children } = $props(); 191 </script> 192 193 <nav> 194 <a href="/">Home</a> 195 <a href="/about">About</a> 196 <a href="/settings">Settings</a> 197 </nav> 198 199 {@render children()} 200 ``` 201 202 If we create pages for `/`, `/about` and `/settings`... 203 204 ```html 205 /// file: src/routes/+page.svelte 206 <h1>Home</h1> 207 ``` 208 209 ```html 210 /// file: src/routes/about/+page.svelte 211 <h1>About</h1> 212 ``` 213 214 ```html 215 /// file: src/routes/settings/+page.svelte 216 <h1>Settings</h1> 217 ``` 218 219 ...the nav will always be visible, and clicking between the three pages will only result in the `<h1>` being replaced. 220 221 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](https://github.com/settings)). 222 223 We can create a layout that only applies to pages below `/settings` (while inheriting the root layout with the top-level nav): 224 225 ```svelte 226 <!--- file: src/routes/settings/+layout.svelte ---> 227 <script> 228 /** @type {import('./$types').LayoutProps} */ 229 let { data, children } = $props(); 230 </script> 231 232 <h1>Settings</h1> 233 234 <div class="submenu"> 235 {#each data.sections as section} 236 <a href="/settings/{section.slug}">{section.title}</a> 237 {/each} 238 </div> 239 240 {@render children()} 241 ``` 242 243 > [!LEGACY] 244 > `LayoutProps` was added in 2.16.0. In earlier versions, you had to [type the properties manually instead](#$types). 245 246 You can see how `data` is populated by looking at the `+layout.js` example in the next section just below. 247 248 By default, each layout inherits the layout above it. Sometimes that isn't what you want - in this case, [advanced layouts](advanced-routing#Advanced-layouts) can help you. 249 250 ### +layout.js 251 252 Just like `+page.svelte` loading data from `+page.js`, your `+layout.svelte` component can get data from a [`load`](load) function in `+layout.js`. 253 254 ```js 255 /// file: src/routes/settings/+layout.js 256 /** @type {import('./$types').LayoutLoad} */ 257 export function load() { 258 return { 259 sections: [ 260 { slug: 'profile', title: 'Profile' }, 261 { slug: 'notifications', title: 'Notifications' } 262 ] 263 }; 264 } 265 ``` 266 267 If a `+layout.js` exports [page options](page-options) — `prerender`, `ssr` and `csr` — they will be used as defaults for child pages. 268 269 Data returned from a layout's `load` function is also available to all its child pages: 270 271 ```svelte 272 <!--- file: src/routes/settings/profile/+page.svelte ---> 273 <script> 274 /** @type {import('./$types').PageProps} */ 275 let { data } = $props(); 276 277 console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...] 278 </script> 279 ``` 280 281 > [!NOTE] Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently rerun [`load`](load) functions when necessary. 282 283 ### +layout.server.js 284 285 To run your layout's `load` function on the server, move it to `+layout.server.js`, and change the `LayoutLoad` type to `LayoutServerLoad`. 286 287 Like `+layout.js`, `+layout.server.js` can export [page options](page-options) — `prerender`, `ssr` and `csr`. 288 289 ## +server 290 291 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 exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `OPTIONS`, and `HEAD` that take a [`RequestEvent`](@sveltejs-kit#RequestEvent) argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object. 292 293 For example we could create an `/api/random-number` route with a `GET` handler: 294 295 ```js 296 /// file: src/routes/api/random-number/+server.js 297 import { error } from '@sveltejs/kit'; 298 299 /** @type {import('./$types').RequestHandler} */ 300 export function GET({ url }) { 301 const min = Number(url.searchParams.get('min') ?? '0'); 302 const max = Number(url.searchParams.get('max') ?? '1'); 303 304 const d = max - min; 305 306 if (isNaN(d) || d < 0) { 307 error(400, 'min and max must be numbers, and min must be less than max'); 308 } 309 310 const random = min + Math.random() * d; 311 312 return new Response(String(random)); 313 } 314 ``` 315 316 The first argument to `Response` can be a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/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). 317 318 You can use the [`error`](@sveltejs-kit#error), [`redirect`](@sveltejs-kit#redirect) and [`json`](@sveltejs-kit#json) methods from `@sveltejs/kit` for convenience (but you don't have to). 319 320 If an error is thrown (either `error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors). 321 322 > [!NOTE] When creating an `OPTIONS` handler, note that Vite will inject `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods` headers — these will not be present in production unless you add them. 323 324 > [!NOTE] `+layout` files have no effect on `+server.js` files. If you want to run some logic before each request, add it to the server [`handle`](hooks#Server-hooks-handle) hook. 325 326 ### Receiving data 327 328 By exporting `POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`/`HEAD` handlers, `+server.js` files can be used to create a complete API: 329 330 ```svelte 331 <!--- file: src/routes/add/+page.svelte ---> 332 <script> 333 let a = $state(0); 334 let b = $state(0); 335 let total = $state(0); 336 337 async function add() { 338 const response = await fetch('/api/add', { 339 method: 'POST', 340 body: JSON.stringify({ a, b }), 341 headers: { 342 'content-type': 'application/json' 343 } 344 }); 345 346 total = await response.json(); 347 } 348 </script> 349 350 <input type="number" bind:value={a} /> + 351 <input type="number" bind:value={b} /> = 352 {total} 353 354 <button onclick={add}>Calculate</button> 355 ``` 356 357 ```js 358 /// file: src/routes/api/add/+server.js 359 import { json } from '@sveltejs/kit'; 360 361 /** @type {import('./$types').RequestHandler} */ 362 export async function POST({ request }) { 363 const { a, b } = await request.json(); 364 return json(a + b); 365 } 366 ``` 367 368 > [!NOTE] In general, [form actions](form-actions) are a better way to submit data from the browser to the server. 369 370 > [!NOTE] If a `GET` handler is exported, a `HEAD` request will return the `content-length` of the `GET` handler's response body. 371 372 ### Fallback method handler 373 374 Exporting the `fallback` handler will match any unhandled request methods, including methods like `MOVE` which have no dedicated export from `+server.js`. 375 376 ```js 377 /// file: src/routes/api/add/+server.js 378 import { json, text } from '@sveltejs/kit'; 379 380 /** @type {import('./$types').RequestHandler} */ 381 export async function POST({ request }) { 382 const { a, b } = await request.json(); 383 return json(a + b); 384 } 385 386 // This handler will respond to PUT, PATCH, DELETE, etc. 387 /** @type {import('./$types').RequestHandler} */ 388 export async function fallback({ request }) { 389 return text(`I caught your ${request.method} request!`); 390 } 391 ``` 392 393 > [!NOTE] For `HEAD` requests, the `GET` handler takes precedence over the `fallback` handler. 394 395 ### Content negotiation 396 397 `+server.js` files can be placed in the same directory as `+page` files, allowing the same route to be either a page or an API endpoint. To determine which, SvelteKit applies the following rules: 398 399 - `PUT`/`PATCH`/`DELETE`/`OPTIONS` requests are always handled by `+server.js` since they do not apply to pages 400 - `GET`/`POST`/`HEAD` requests are treated as page requests if the `accept` header prioritises `text/html` (in other words, it's a browser page request), else they are handled by `+server.js`. 401 - Responses to `GET` requests will include a `Vary: Accept` header, so that proxies and browsers cache HTML and JSON responses separately. 402 403 ## $types 404 405 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. 406 407 For example, annotating `let { data } = $props()` with `PageProps` (or `LayoutProps`, for a `+layout.svelte` file) tells TypeScript that the type of `data` is whatever was returned from `load`: 408 409 ```svelte 410 <!--- file: src/routes/blog/[slug]/+page.svelte ---> 411 <script> 412 /** @type {import('./$types').PageProps} */ 413 let { data } = $props(); 414 </script> 415 ``` 416 417 > [!NOTE] 418 > The `PageProps` and `LayoutProps` types, added in 2.16.0, are a shortcut for typing the `data` prop as `PageData` or `LayoutData`, as well as other props, such as `form` for pages, or `children` for layouts. In earlier versions, you had to type these properties manually. For example, for a page: 419 > 420 > ```js 421 > /// file: +page.svelte 422 > /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ 423 > let { data, form } = $props(); 424 > ``` 425 > 426 > Or, for a layout: 427 > 428 > ```js 429 > /// file: +layout.svelte 430 > /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ 431 > let { data, children } = $props(); 432 > ``` 433 434 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. 435 436 If you're using VS Code or any IDE that supports the language server protocol and TypeScript plugins then you can omit these types _entirely_! Svelte's IDE tooling will insert the correct types for you, so you'll get type checking without writing them yourself. It also works with our command line tool `svelte-check`. 437 438 You can read more about omitting `$types` in our [blog post](/blog/zero-config-type-safety) about it. 439 440 ## Other files 441 442 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. 443 444 If components and modules are needed by multiple routes, it's a good idea to put them in [`$lib`]($lib). 445 446 ## Further reading 447 448 - [Tutorial: Routing](/tutorial/kit/pages) 449 - [Tutorial: API routes](/tutorial/kit/get-handlers) 450 - [Docs: Advanced routing](advanced-routing)