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

FileWhere it runsWhat it does
+page.svelteServer and clientMarkup for the route
+page.server.jsServer onlyload with database and secrets
+page.jsServer and clientIsomorphic load for public data
+layout.svelteServer and clientFrame shared by children
+error.svelteServer and clientError 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.js when you read the database or a secret: that code never reaches the browser.
  • +page.js when the fetch is public and you want the same logic on both sides.
  • ./$types to type params and data: the types are generated, not written by hand.
  • +layout.server.js for 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 fetch in onMount when 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 params as 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.