Back to skills
SkillHub ClubBuild UIFrontend

sveltekit-structure

Provides quick reference documentation for SvelteKit file structure and routing patterns. Covers core concepts like page/layout files, route parameters, error boundaries, and SSR hydration. Includes practical examples and separates detailed explanations into referenced files.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
192
Hot score
97
Updated
March 20, 2026
Overall rating
A8.5
Composite score
7.0
Best-practice grade
B75.6

Install command

npx @skill-hub/cli install spences10-svelte-claude-skills-sveltekit-structure
sveltekitweb-developmentdocumentationroutingerror-handling

Repository

spences10/svelte-claude-skills

Skill path: .claude/skills/sveltekit-structure

Provides quick reference documentation for SvelteKit file structure and routing patterns. Covers core concepts like page/layout files, route parameters, error boundaries, and SSR hydration. Includes practical examples and separates detailed explanations into referenced files.

Open repository

Best for

Primary workflow: Build UI.

Technical facets: Frontend.

Target audience: SvelteKit developers needing quick reference for routing, layouts, and error handling patterns.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: spences10.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install sveltekit-structure into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/spences10/svelte-claude-skills before adding sveltekit-structure to shared team environments
  • Use sveltekit-structure for frontend workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: sveltekit-structure
# IMPORTANT: Keep description on ONE line for Claude Code compatibility
# prettier-ignore
description: SvelteKit structure guidance. Use for routing, layouts, error handling, SSR, or svelte:boundary. Covers file naming, nested layouts, error boundaries, pending UI, and hydration.
---

# SvelteKit Structure

## Quick Start

**File types:** `+page.svelte` (page) | `+layout.svelte` (wrapper) |
`+error.svelte` (error boundary) | `+server.ts` (API endpoint)

**Routes:** `src/routes/about/+page.svelte` → `/about` |
`src/routes/posts/[id]/+page.svelte` → `/posts/123`

**Layouts:** Apply to all child routes. `+layout.svelte` at any level
wraps descendants.

## Example

```
src/routes/
├── +layout.svelte              # Root layout (all pages)
├── +page.svelte                # Homepage /
├── about/+page.svelte          # /about
└── dashboard/
    ├── +layout.svelte          # Dashboard layout (dashboard pages only)
    ├── +page.svelte            # /dashboard
    └── settings/+page.svelte   # /dashboard/settings
```

```svelte
<!-- +layout.svelte -->
<script>
	let { children } = $props();
</script>

<nav><!-- Navigation --></nav>
<main>{@render children()}</main>
<footer><!-- Footer --></footer>
```

## Reference Files

- [file-naming.md](references/file-naming.md) - File naming
  conventions
- [layout-patterns.md](references/layout-patterns.md) - Nested layouts
- [error-handling.md](references/error-handling.md) - +error.svelte
  placement
- [svelte-boundary.md](references/svelte-boundary.md) -
  Component-level error/pending
- [ssr-hydration.md](references/ssr-hydration.md) - SSR and
  browser-only code

## Notes

- Layouts: `{@render children()}` | Errors: +error.svelte _above_
  failing route
- Groups: `(name)` folders don't affect URL | Client-only: check
  `browser`
- **Last verified:** 2025-01-11

<!--
PROGRESSIVE DISCLOSURE GUIDELINES:
- Keep this file ~50 lines total (max ~150 lines)
- Use 1-2 code blocks only (recommend 1)
- Keep description <200 chars for Level 1 efficiency
- Move detailed docs to references/ for Level 3 loading
- This is Level 2 - quick reference ONLY, not a manual

LLM WORKFLOW (when editing this file):
1. Write/edit SKILL.md
2. Format (if formatter available)
3. Run: claude-skills-cli validate <path>
4. If multi-line description warning: run claude-skills-cli doctor <path>
5. Validate again to confirm
-->


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/file-naming.md

```markdown
# File Naming Conventions

## Core Files

| File                | Purpose               | Runs                  | Example                                       |
| ------------------- | --------------------- | --------------------- | --------------------------------------------- |
| `+page.svelte`      | Page component        | Client & Server (SSR) | `/routes/about/+page.svelte` → `/about`       |
| `+page.ts`          | Universal load        | Client & Server       | Data for +page.svelte                         |
| `+page.server.ts`   | Server load & actions | Server only           | DB queries, form actions                      |
| `+layout.svelte`    | Layout wrapper        | Client & Server       | Wraps child routes                            |
| `+layout.ts`        | Layout universal load | Client & Server       | Data for +layout.svelte                       |
| `+layout.server.ts` | Layout server load    | Server only           | Auth, user data                               |
| `+error.svelte`     | Error boundary        | Client & Server       | Shown when error thrown                       |
| `+server.ts`        | API endpoint          | Server only           | `/routes/api/users/+server.ts` → `/api/users` |

## Route Parameters

| Pattern        | Matches        | Example                                                          |
| -------------- | -------------- | ---------------------------------------------------------------- |
| `[id]`         | Single param   | `/posts/[id]/+page.svelte` → `/posts/123`                        |
| `[slug]`       | Single param   | `/blog/[slug]/+page.svelte` → `/blog/hello-world`                |
| `[[optional]]` | Optional param | `/search/[[query]]/+page.svelte` → `/search` or `/search/svelte` |
| `[...rest]`    | Rest params    | `/docs/[...path]/+page.svelte` → `/docs/a/b/c`                   |

## Route Groups

| Pattern       | Purpose                      | URL                                            |
| ------------- | ---------------------------- | ---------------------------------------------- |
| `(group)`     | Group routes (no URL impact) | `/(app)/dashboard/+page.svelte` → `/dashboard` |
| `(marketing)` | Separate layouts             | Different layout for marketing pages           |

## Special Files

- `hooks.server.ts` - Server hooks (handle function, runs on every
  request)
- `hooks.client.ts` - Client hooks (runs in browser)
- `app.html` - HTML template
- `service-worker.ts` - Service worker
- `params/*.ts` - Param validators

## Common Patterns

```
src/routes/
├── +layout.svelte              # Root layout
├── +layout.ts                  # Root data
├── +page.svelte                # Homepage
├── +error.svelte               # Root error boundary
│
├── (app)/                      # App routes (grouped)
│   ├── +layout.svelte          # App layout (auth required)
│   ├── dashboard/+page.svelte  # /dashboard
│   └── settings/+page.svelte   # /settings
│
├── (marketing)/                # Marketing routes (grouped)
│   ├── +layout.svelte          # Marketing layout
│   ├── about/+page.svelte      # /about
│   └── pricing/+page.svelte    # /pricing
│
├── blog/
│   ├── +page.svelte            # /blog (list)
│   └── [slug]/
│       ├── +page.svelte        # /blog/post-title
│       └── +page.server.ts     # Load post data
│
└── api/
    └── posts/
        └── +server.ts          # API: GET/POST /api/posts
```

```

### references/layout-patterns.md

```markdown
# Layout Patterns

## Basic Layout

```svelte
<!-- src/routes/+layout.svelte -->
<script>
	let { children } = $props();
</script>

<header>Header</header>
<main>{@render children()}</main>
<footer>Footer</footer>
```

**Key points:**

- Must declare `children` in `$props()`
- Use `{@render children()}` to render nested content
- Root layout wraps ALL pages

## Nested Layouts

Layouts inherit from parent layouts:

```
src/routes/
├── +layout.svelte          # Root layout (all pages)
└── dashboard/
    ├── +layout.svelte      # Dashboard layout (dashboard pages only)
    └── +page.svelte        # Uses both layouts
```

**Result:** Root layout wraps dashboard layout wraps page.

**Rendering order:**

```
Root +layout.svelte
  └─ Dashboard +layout.svelte
      └─ +page.svelte
```

### Example: Nested Layout Pattern

```svelte
<!-- src/routes/+layout.svelte -->
<script>
	let { children } = $props();
</script>

<div class="app">
	<nav>Global Nav</nav>
	{@render children()}
</div>
```

```svelte
<!-- src/routes/dashboard/+layout.svelte -->
<script>
	let { children } = $props();
</script>

<div class="dashboard">
	<aside>Dashboard Sidebar</aside>
	<main>{@render children()}</main>
</div>
```

```svelte
<!-- src/routes/dashboard/+page.svelte --><h1>Dashboard Home</h1>
```

**Rendered HTML structure:**

```html
<div class="app">
	<nav>Global Nav</nav>
	<div class="dashboard">
		<aside>Dashboard Sidebar</aside>
		<main>
			<h1>Dashboard Home</h1>
		</main>
	</div>
</div>
```

## Layout Groups

Use `(groups)` to organize without affecting URLs:

```
src/routes/
├── (marketing)/
│   ├── +layout.svelte      # Marketing layout
│   ├── about/+page.svelte  # /about (uses marketing layout)
│   └── pricing/+page.svelte # /pricing (uses marketing layout)
│
└── (app)/
    ├── +layout.svelte      # App layout
    ├── dashboard/+page.svelte  # /dashboard (uses app layout)
    └── settings/+page.svelte   # /settings (uses app layout)
```

**Key points:**

- Parentheses make groups invisible in URLs
- `/about` route, NOT `/(marketing)/about`
- Different layouts for different sections
- Useful for authentication boundaries

### Example: Marketing vs App Layouts

```svelte
<!-- src/routes/(marketing)/+layout.svelte -->
<script>
	let { children } = $props();
</script>

<header class="marketing-header">
	<nav>
		<a href="/">Home</a>
		<a href="/about">About</a>
		<a href="/login">Login</a>
	</nav>
</header>

{@render children()}

<footer>© 2024 Company</footer>
```

```svelte
<!-- src/routes/(app)/+layout.svelte -->
<script>
	let { children, data } = $props();
</script>

<header class="app-header">
	<nav>
		<a href="/dashboard">Dashboard</a>
		<a href="/settings">Settings</a>
		<span>Welcome, {data.user.name}</span>
	</nav>
</header>

{@render children()}
```

## Reset Layout

Use `@` in filename to break layout inheritance:

**NOT RECOMMENDED** - This feature is deprecated in SvelteKit 2+.

Instead, use layout groups to create separate hierarchies:

```
src/routes/
├── +layout.svelte          # Root layout (for most pages)
├── (app)/
│   └── +layout.svelte      # App layout
└── (public)/
    └── +layout.svelte      # Public layout (completely separate)
```

## Layout with Data Loading

```svelte
<!-- src/routes/+layout.server.ts -->
export const load = async ({ locals }) => {
	// Available to all child routes
	return {
		user: locals.user,
	};
};
```

```svelte
<!-- src/routes/+layout.svelte -->
<script>
	let { children, data } = $props();
</script>

<header>
	{#if data.user}
		<span>Welcome, {data.user.name}</span>
	{:else}
		<a href="/login">Login</a>
	{/if}
</header>

{@render children()}
```

## Conditional Layout Content

```svelte
<!-- +layout.svelte -->
<script>
	import { page } from '$app/stores';
	let { children } = $props();
</script>

{#if !$page.url.pathname.startsWith('/admin')}
	<header>Public Header</header>
{/if}

{@render children()}

{#if !$page.url.pathname.includes('/checkout')}
	<footer>Footer</footer>
{/if}
```

**Better approach:** Use layout groups instead of conditionals.

## Protected Layouts

```typescript
// src/routes/(app)/+layout.server.ts
import { redirect } from '@sveltejs/kit';

export const load = async ({ locals }) => {
	if (!locals.user) {
		throw redirect(303, '/login');
	}

	return {
		user: locals.user,
	};
};
```

All routes under `(app)` group now require authentication.

## Sharing Layout State

```svelte
<!-- src/routes/+layout.svelte -->
<script>
	import { setContext } from 'svelte';
	let { children, data } = $props();

	// Share state with all descendant components
	setContext('user', data.user);
</script>

{@render children()}
```

```svelte
<!-- Any child component -->
<script>
	import { getContext } from 'svelte';
	const user = getContext('user');
</script>

<p>Hello, {user.name}</p>
```

## Layout Slot Props (Snippets)

```svelte
<!-- src/routes/dashboard/+layout.svelte -->
<script>
	let { children, header } = $props();
</script>

<div class="dashboard">
	<aside>Sidebar</aside>
	<div class="content">
		{#if header}
			<header>{@render header()}</header>
		{/if}
		<main>{@render children()}</main>
	</div>
</div>
```

```svelte
<!-- src/routes/dashboard/+page.svelte -->
{#snippet header()}
	<h1>Custom Dashboard Header</h1>
{/snippet}

<p>Dashboard content</p>
```

## Common Patterns

### Pattern 1: Auth Boundary

```
src/routes/
├── +layout.svelte           # Root (no auth)
├── (public)/
│   ├── +layout.svelte       # Public layout (landing pages)
│   ├── +page.svelte         # /
│   └── about/+page.svelte   # /about
└── (protected)/
    ├── +layout.server.ts    # Check auth, redirect if not logged in
    ├── +layout.svelte       # Protected layout (app UI)
    ├── dashboard/+page.svelte  # /dashboard
    └── settings/+page.svelte   # /settings
```

### Pattern 2: Multi-tenant with Shared Root

```
src/routes/
├── +layout.svelte           # Root layout (shared)
├── admin/
│   ├── +layout.svelte       # Admin layout
│   └── users/+page.svelte   # /admin/users
└── client/
    ├── +layout.svelte       # Client layout
    └── projects/+page.svelte # /client/projects
```

### Pattern 3: Progressive Enhancement

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

<div class="app">
	{#if $navigating}
		<div class="loading-bar"></div>
	{/if}

	{@render children()}
</div>

<style>
	.loading-bar {
		position: fixed;
		top: 0;
		left: 0;
		width: 100%;
		height: 3px;
		background: blue;
		animation: loading 1s ease-in-out;
	}
</style>
```

## Layout Best Practices

1. ✅ Keep root layout minimal (shared across ALL pages)
2. ✅ Use layout groups for separate sections
3. ✅ Load shared data in layout's load function
4. ✅ Use context for sharing state with descendants
5. ✅ Avoid too many nested layouts (max 2-3 levels)
6. ✅ Don't put auth logic in root layout (use groups)
7. ✅ Remember: layouts share data DOWN, not UP
8. ❌ Avoid conditionals in layouts (use groups instead)

## Debugging Layouts

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

	$effect(() => {
		console.log('Current route:', $page.url.pathname);
		console.log('Layout data:', $page.data);
	});
</script>

{@render children()}
```

## Key Takeaways

- Layouts wrap all child routes
- Nested layouts create hierarchy (root → section → page)
- Groups `(name)` organize without affecting URLs
- Load data in `+layout.server.ts` to share with children
- Use context to share reactive state
- Keep layouts simple and focused

```

### references/error-handling.md

```markdown
# Error Handling

## Error Boundary Placement

**Key rule:** `+error.svelte` must be _above_ the failing route in the
hierarchy.

```
src/routes/
├── +error.svelte           # Catches errors in all routes below
├── +page.svelte            # If this errors → uses +error.svelte above
└── admin/
    ├── +error.svelte       # Catches errors in admin routes
    └── +page.svelte        # If this errors → uses admin/+error.svelte
```

**Wrong:**

```
src/routes/dashboard/
├── +layout.svelte          # If this errors...
└── +error.svelte           # This won't catch it (too low)
```

**Right:**

```
src/routes/
├── +error.svelte           # Catches dashboard layout errors
└── dashboard/
    ├── +layout.svelte
    └── +error.svelte       # Catches dashboard page errors
```

## Error Propagation

Errors bubble up to the nearest `+error.svelte`:

```
src/routes/
├── +error.svelte                    # Level 1 (root fallback)
└── blog/
    ├── +error.svelte                # Level 2 (blog fallback)
    └── [slug]/
        ├── +layout.server.ts        # Error here → blog/+error.svelte
        ├── +page.server.ts          # Error here → blog/+error.svelte
        └── +page.svelte             # Error here → blog/+error.svelte
```

**If no error boundary exists at that level, it goes to the parent.**

## Basic Error Page

```svelte
<!-- +error.svelte -->
<script>
	import { page } from '$app/stores';
</script>

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

## Custom Error Data

```typescript
// +page.server.ts
import { error } from '@sveltejs/kit';

export const load = async ({ params }) => {
	const post = await getPost(params.id);

	if (!post) {
		throw error(404, {
			message: 'Post not found',
			postId: params.id,
		});
	}

	return { post };
};
```

```svelte
<!-- +error.svelte -->
<script>
	import { page } from '$app/stores';
</script>

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

{#if $page.error.postId}
	<p>Could not find post with ID: {$page.error.postId}</p>
{/if}
```

## Status Code Specific Errors

```svelte
<!-- +error.svelte -->
<script>
	import { page } from '$app/stores';
</script>

{#if $page.status === 404}
	<h1>Page Not Found</h1>
	<p>The page you're looking for doesn't exist.</p>
	<a href="/">Go home</a>
{:else if $page.status === 403}
	<h1>Access Denied</h1>
	<p>You don't have permission to view this page.</p>
{:else if $page.status === 401}
	<h1>Unauthorized</h1>
	<p>Please log in to continue.</p>
	<a href="/login">Login</a>
{:else if $page.status >= 500}
	<h1>Server Error</h1>
	<p>Something went wrong on our end. Please try again later.</p>
{:else}
	<h1>Error {$page.status}</h1>
	<p>{$page.error.message}</p>
{/if}
```

## Common Status Codes

- **400** - Bad Request (malformed request)
- **401** - Unauthorized (not logged in)
- **403** - Forbidden (logged in but no permission)
- **404** - Not Found (resource doesn't exist)
- **500** - Internal Server Error (unhandled exception)
- **503** - Service Unavailable (server overloaded/down)

## Error in Layout vs Page

**Layout error:**

```typescript
// src/routes/dashboard/+layout.server.ts
export const load = async ({ locals }) => {
	if (!locals.user) {
		throw error(401, 'Not logged in'); // Caught by +error.svelte ABOVE dashboard
	}
	return { user: locals.user };
};
```

**Page error:**

```typescript
// src/routes/dashboard/settings/+page.server.ts
export const load = async () => {
	throw error(404, 'Settings not found'); // Caught by nearest +error.svelte
};
```

## Expected Errors vs Unexpected Errors

**Expected (use error()):**

```typescript
// User requests invalid resource
if (!post) throw error(404, 'Post not found');

// User lacks permission
if (post.authorId !== user.id) throw error(403, 'Not your post');
```

**Unexpected (let it bubble):**

```typescript
// Unhandled exceptions (DB connection fails, etc.)
// Will show generic 500 error
const posts = await db.query.posts.findMany(); // Might throw
```

## Handling Errors in handleError Hook

```typescript
// src/hooks.server.ts
import type { HandleServerError } from '@sveltejs/kit';

export const handleError: HandleServerError = ({ error, event }) => {
	// Log to error tracking service
	console.error('Error:', error, 'Path:', event.url.pathname);

	// Return user-friendly message (don't expose internals)
	return {
		message: 'An unexpected error occurred',
		code: error?.code ?? 'UNKNOWN',
	};
};
```

## Error in Load vs Error in Component

**Load function error (recommended):**

```typescript
// +page.server.ts
export const load = async ({ params }) => {
	const user = await getUser(params.id);
	if (!user) throw error(404, 'User not found'); // Shows +error.svelte
	return { user };
};
```

**Component error (not ideal):**

```svelte
<!-- +page.svelte - Don't do this -->
<script>
	export let data;

	// If data.user is undefined, component just shows nothing
	// No error boundary triggered
</script>

<h1>{data.user.name}</h1> <!-- Might crash if user undefined -->
```

**Best practice:** Validate and throw errors in `load` functions, not
components.

## Fallback Error Handling

Always have a root `+error.svelte`:

```svelte
<!-- src/routes/+error.svelte -->
<script>
	import { page } from '$app/stores';
	import { dev } from '$app/environment';
</script>

<h1>Oops! Something went wrong</h1>

{#if dev}
	<!-- Show details in dev mode -->
	<pre>{JSON.stringify($page.error, null, 2)}</pre>
{:else}
	<!-- Generic message in production -->
	<p>We're sorry, but something unexpected happened.</p>
{/if}

<a href="/">Go home</a>
```

## Testing Error Boundaries

```typescript
// +page.server.ts
export const load = async ({ url }) => {
	// Test error page in dev
	if (url.searchParams.has('test-error')) {
		throw error(500, 'Test error');
	}

	return {};
};
```

Visit `http://localhost:5173/page?test-error` to see error boundary.

## Key Takeaways

1. ✅ Place `+error.svelte` ABOVE failing routes
2. ✅ Errors bubble up to nearest error boundary
3. ✅ Always have a root `src/routes/+error.svelte`
4. ✅ Throw errors in `load` functions, not components
5. ✅ Use specific status codes (401, 403, 404, 500)
6. ✅ Customize error pages per section (blog, admin, etc.)
7. ✅ Use `handleError` hook for logging/monitoring
8. ✅ Show detailed errors in dev, generic in production

```

### references/svelte-boundary.md

```markdown
# svelte:boundary Component

> Available in Svelte 5.3+

## Two Purposes

1. **Error boundaries** - catch rendering errors
2. **Pending UI** - show loading state while `await` resolves

## Basic Error Boundary

```svelte
<svelte:boundary onerror={(e, reset) => console.error(e)}>
	<RiskyComponent />

	{#snippet failed(error, reset)}
		<p>Error: {error.message}</p>
		<button onclick={reset}>Try again</button>
	{/snippet}
</svelte:boundary>
```

## Pending UI (Loading States)

```svelte
<svelte:boundary>
	{#await loadData()}
		<!-- This shows while loading -->
	{:then data}
		<DataView {data} />
	{/await}

	{#snippet pending()}
		<LoadingSpinner />
	{/snippet}
</svelte:boundary>
```

## Combined Error + Pending

```svelte
<svelte:boundary onerror={logError}>
	{#await fetchUser()}
		<!-- Will show pending snippet -->
	{:then user}
		<UserProfile {user} />
	{/await}

	{#snippet pending()}
		<p>Loading user...</p>
	{/snippet}

	{#snippet failed(error, reset)}
		<p>Failed to load user</p>
		<button onclick={reset}>Retry</button>
	{/snippet}
</svelte:boundary>
```

## What Gets Caught

**Caught:**

- Errors during rendering
- Errors in `$effect`

**NOT Caught:**

- Event handler errors (`onclick`, etc.)
- Errors after `setTimeout`
- Async errors outside boundary's await

## vs +error.svelte

| Feature  | svelte:boundary         | +error.svelte |
| -------- | ----------------------- | ------------- |
| Scope    | Component subtree       | Route segment |
| Reset    | Built-in reset function | Navigate away |
| Pending  | Yes (pending snippet)   | No            |
| Use case | Component-level         | Page-level    |

## Error Tracking Integration

```svelte
<svelte:boundary
	onerror={(error, reset) => {
		// Send to Sentry, LogRocket, etc.
		errorTracker.captureException(error);
	}}
>
	<App />

	{#snippet failed(error, reset)}
		<ErrorFallback {error} {reset} />
	{/snippet}
</svelte:boundary>
```

## Nested Boundaries

Inner boundary catches first:

```svelte
<svelte:boundary>
	<!-- Outer fallback -->
	{#snippet failed(e)}
		<p>Outer caught: {e.message}</p>
	{/snippet}

	<svelte:boundary>
		<!-- Inner fallback -->
		{#snippet failed(e)}
			<p>Inner caught: {e.message}</p>
		{/snippet}

		<ComponentThatMightFail />
	</svelte:boundary>
</svelte:boundary>
```

## Key Points

- Use `svelte:boundary` for component-level error isolation
- Use `+error.svelte` for route-level error pages
- `pending` snippet shows during initial `await` resolution
- `failed` snippet replaces content on error
- `reset` function lets users retry
- Errors in event handlers are NOT caught

```

### references/ssr-hydration.md

```markdown
# SSR & Hydration

## The Problem

SvelteKit runs on server (SSR) then hydrates in browser. Code using
browser APIs (`window`, `document`, `localStorage`) fails on server.

## Solution: Check for Browser

```typescript
import { browser } from '$app/environment';

// In load function
export const load = async () => {
	const theme = browser ? localStorage.getItem('theme') : 'light';
	return { theme };
};
```

```svelte
<!-- In component -->
<script>
	import { browser } from '$app/environment';
	import { onMount } from 'svelte';

	let data = $state(null);

	// Option 1: Use browser check
	if (browser) {
		data = localStorage.getItem('data');
	}

	// Option 2: Use onMount (only runs in browser)
	onMount(() => {
		data = localStorage.getItem('data');
	});

	// Option 3: Use $effect with browser check
	$effect(() => {
		if (browser) {
			data = localStorage.getItem('data');
		}
	});
</script>
```

## Common Mistakes

### ❌ Using window Without Check

```typescript
// WRONG - fails on server
export const load = async () => {
	const width = window.innerWidth; // ERROR on server
	return { width };
};

// RIGHT
import { browser } from '$app/environment';

export const load = async () => {
	const width = browser ? window.innerWidth : 1024;
	return { width };
};
```

### ❌ Accessing DOM in Load

```typescript
// WRONG
export const load = async () => {
	const el = document.getElementById('root'); // ERROR on server
};

// RIGHT - Do DOM stuff in onMount
export const load = async () => {
	return {};
};
// Then in component:
onMount(() => {
	const el = document.getElementById('root');
});
```

## Disable SSR (Not Recommended)

```typescript
// +page.ts
export const ssr = false; // Disables SSR for this page
```

Only use when absolutely necessary (e.g., heavy Canvas/WebGL).

```