React
Routing and layouts in SvelteKit: files, segments and resets
In SvelteKit the folder tree is the router: +page, +layout and +page@ decide the URL, the data and which layouts stay mounted.
Key points
- The URL comes from the folder: [slug] is a parameter, (app) a group that never appears in the URL.
- A load that touches secrets belongs in +page.server.js; +page.js also runs in the browser.
- [email protected] sends a page back to the root layout and skips the ones in between.
In SvelteKit the URL comes from the folder: src/routes/blog/[slug]/+page.svelte answers /blog/anything with no route file to maintain. The file name also says when that code runs, on the server or in the browser.
The files that matter
| File | Where it runs | What it does |
|---|---|---|
| +page.svelte | Server and client | Markup for the route |
| +page.server.js | Server only | load with database and secrets |
| +page.js | Server and client | Isomorphic load for public data |
| +layout.svelte | Server and client | Frame shared by children |
| +error.svelte | Server and client | Error for the segment |
src/routes/
+layout.svelte
+layout.server.js
(app)/
dashboard/
+layout.svelte
+page.svelte
settings/
+page@(app).svelte
blog/
[slug]/
+page.server.js
+page.svelte
(app) is a route group: it adds no segment to the URL and gives several branches a shared layout. [slug] is a parameter, [[lang]] is optional, [...rest] takes the rest of the path.
// src/routes/blog/[slug]/+page.server.js
import { error } from "@sveltejs/kit";
export async function load({ params }) {
const post = await db.post.findUnique({ where: { slug: params.slug } });
if (!post) error(404, "Not found");
return { post };
}
Nested layouts and resets
A layout applies to itself and to every child. [email protected] sends the page back to the root layout; +page@(app).svelte sends it back to the group layout: the layouts in between are not mounted, and the page inherits none of their markup.
Where the load belongs
+page.server.jswhen you read the database or a secret: that code never reaches the browser.+page.jswhen the fetch is public and you want the same logic on both sides../$typesto typeparamsanddata: the types are generated, not written by hand.+layout.server.jsfor shared data, knowing it runs on every navigation inside its branch.
The limitation to plan for
A universal load runs twice, first on the server and then in the browser: it is not the place for a secret. A layout load runs on every navigation inside its branch unless you invalidate it on purpose: if the data is slow, put it in the page that uses it.
Common mistakes
- Returning a function from a load: it gets serialised and the client receives nothing.
- Calling
fetchinonMountwhen a load already does the work: the page shows two loading states. - Putting the auth guard only in the layout: form actions go through an endpoint, not through the layout.
- Reading
paramsas a number: it is a string, convert it yourself.
Layouts are assembled from ready-made blocks: sidebar and dashboard cover the two recurring frames, while flowbite-svelte supplies the Svelte primitives to fill them. State inside routes is in Svelte 5 runes, the older store model in a store that does not update, the comparison in Svelte vs React.
Frequently asked questions
Where do I put code shared between pages?
In a layout when it is interface, in a layout load when it is shared data. Logic that is neither goes into a module under lib, importable from both.
How do I get a URL with no prefix?
With a route group in brackets: the (app) folder never appears in the URL. It also lets a branch use a different layout.
Why does the layout mount again during navigation?
Because you are entering a branch with a different layout, or because a key changed. If the layout depends on a parameter, SvelteKit recreates it: move that part into the page.
Can I use +page.js and +page.server.js together?
Yes, but the server load runs first and passes data to the universal one. Pick one file per route when you can: two loads in the same route are hard to follow.
How do I type params?
By importing the generated types from ./$types: PageServerLoad and PageData follow the route name. Do not write them by hand, they change with the structure.