04 - Layouts

What are Layouts?

Layouts wrap pages with shared UI (nav bars, sidebars, footers). They persist across navigation — only the page content inside changes. Layouts can be nested, and each level can load its own data.

Root Layout

Every SvelteKit app has a root layout that wraps all pages. The children snippet is where the page content renders:

<!-- src/routes/+layout.svelte -->
<script lang="ts">
let { children } = $props();   // <!-- SvelteKit passes this automatically -->
</script>

<header>
  <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
  </nav>
</header>

<main>
  {@render children()}   <!-- renders whatever +page.svelte matches the current URL -->
</main>

<footer>© 2026</footer>

Every page in the app will have this header and footer.

Nested Layouts

A layout in a subfolder wraps only the pages in that folder. Layouts stack — the dashboard layout renders inside the root layout:

src/routes/
├── +layout.svelte           ← Root layout (nav + footer)
├── +page.svelte             ← Home page
└── dashboard/
    ├── +layout.svelte       ← Dashboard layout (sidebar) — nested inside root
    ├── +page.svelte         ← /dashboard
    └── settings/
        └── +page.svelte     ← /dashboard/settings
<!-- src/routes/dashboard/+layout.svelte -->
<script lang="ts">
let { children } = $props();
</script>

<div class="dashboard">
  <aside>
    <a href="/dashboard">Overview</a>
    <a href="/dashboard/settings">Settings</a>
  </aside>
  
  <div class="content">
    {@render children()}  <!-- /dashboard or /dashboard/settings renders here -->
  </div>
</div>

Result: /dashboard/settings renders inside dashboard layout, which renders inside root layout.

Layout Data

Layouts can have their own load functions. The data is available to the layout and all child pages:

// src/routes/+layout.ts
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = async ({ fetch }) => {
  const res = await fetch('/api/user');
  const user = await res.json();
  
  return { user };  // available in +layout.svelte AND all child pages
};
<!-- src/routes/+layout.svelte -->
<script lang="ts">
let { data, children } = $props();  <!-- data = { user } from load -->
</script>

<header>
  <p>Welcome, {data.user.name}</p>
</header>

{@render children()}

Layout Server Data

Use +layout.server.ts for server-only data (DB, secrets, locals):

// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  return {
    user: locals.user  // set by hooks.server.ts (see lesson 08)
  };
};

Accessing Layout Data in Pages

Pages automatically receive data from parent layouts merged with their own data:

<!-- src/routes/dashboard/+page.svelte -->
<script lang="ts">
let { data } = $props();
// data includes both this page's load data AND parent layout data
</script>

<h1>Hello {data.user.name}</h1>  <!-- user came from root layout's load -->

Layout Groups

Parentheses create groups that share a layout without affecting URLs:

src/routes/
├── (marketing)/
│   ├── +layout.svelte       ← simple layout (no sidebar)
│   ├── about/+page.svelte   → /about
│   └── contact/+page.svelte → /contact
└── (app)/
    ├── +layout.svelte       ← app layout (sidebar + auth)
    └── dashboard/+page.svelte → /dashboard

(marketing) and (app) don't appear in URLs — they just group routes under different layouts.

Breaking Out of Layouts

Use @ to skip parent layouts. Rarely needed but useful for special pages:

  • +page@.svelte — uses only the root layout (skips all intermediate layouts)
  • +page@(app).svelte — uses the (app) group layout

Error Boundaries

+error.svelte catches errors from load functions and renders an error page:

<!-- src/routes/+error.svelte -->
<script lang="ts">
import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>

Each layout level can have its own +error.svelte — errors bubble up to the nearest one.

Loading States

Show a loading indicator during navigation using the navigating store:

<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { navigating } from '$app/stores';
let { children } = $props();
</script>

{#if $navigating}
  <div class="loading">Loading...</div>
{/if}

{@render children()}