Compare commits

...

100 Commits

Author SHA1 Message Date
Stanislav Khromov
62c5865ae9 Delete verify-distilled.test.ts 2025-10-14 21:25:10 +02:00
Stanislav Khromov
6b63f1565c format JSON files afte running scripts 2025-10-14 21:21:33 +02:00
Stanislav Khromov
4ba6ab893c format 2025-10-14 21:13:03 +02:00
Stanislav Khromov
931d959e2a update docs 2025-10-14 21:10:55 +02:00
Stanislav Khromov
d690939222 fix linting 2025-10-14 21:05:55 +02:00
Stanislav Khromov
8b80666572 Update .prettierignore 2025-10-14 21:05:27 +02:00
Stanislav Khromov
dc0e8428e5 format 2025-10-14 21:03:13 +02:00
Stanislav Khromov
e1e8476771 Update export-summaries.ts 2025-10-14 21:02:19 +02:00
Stanislav Khromov
a91c80e44b wip 2025-10-14 20:54:21 +02:00
Stanislav Khromov
6f7b24c584 generate markdown summaries 2025-10-14 20:39:55 +02:00
Stanislav Khromov
b5ea3c10db wip 2025-10-14 20:37:32 +02:00
Stanislav Khromov
504413c951 Update package.json 2025-10-14 20:36:56 +02:00
Stanislav Khromov
48230e8c47 clean up 2025-10-14 20:36:47 +02:00
Stanislav Khromov
e72a3d2c4a wip 2025-10-14 20:34:48 +02:00
Stanislav Khromov
ea4ae25035 Merge branch 'main' into improve-use-cases-and-summaries 2025-10-14 20:25:52 +02:00
Stanislav Khromov
b55db13ac9 Update show-verification-errors.ts 2025-10-13 01:23:21 +02:00
Stanislav Khromov
3e8ad5c47d wip 2025-10-13 01:20:33 +02:00
Stanislav Khromov
78be819cbe Delete generate-summaries.test.ts 2025-10-13 01:19:33 +02:00
Stanislav Khromov
0e7d5c3724 Revert "Update get-documentation.ts"
This reverts commit ca578583f2.
2025-10-13 01:09:35 +02:00
Stanislav Khromov
ca578583f2 Update get-documentation.ts 2025-10-13 01:03:04 +02:00
Stanislav Khromov
9208570fe2 Update utils.ts 2025-10-13 00:56:00 +02:00
Stanislav Khromov
28aee8eea6 Update CLAUDE.md 2025-10-13 00:53:51 +02:00
Stanislav Khromov
c05346626c wip 2025-10-13 00:46:14 +02:00
Stanislav Khromov
add959fd02 Update hooks.server.ts 2025-10-13 00:42:31 +02:00
Stanislav Khromov
cdb2a3eea8 wip 2025-10-13 00:36:13 +02:00
Stanislav Khromov
a4bbdf92e1 format 2025-10-13 00:34:57 +02:00
Stanislav Khromov
d5570e23d1 Update distilled.json 2025-10-13 00:34:18 +02:00
Stanislav Khromov
f81c7a2dd5 Update distilled.json 2025-10-13 00:26:21 +02:00
Stanislav Khromov
b11cbbccb5 Update package.json 2025-10-13 00:19:30 +02:00
Stanislav Khromov
b17f1e75ce wip 2025-10-13 00:15:31 +02:00
Stanislav Khromov
19a6c90ebb Update distilled-verification.json 2025-10-13 00:03:38 +02:00
Stanislav Khromov
af573abb82 Update verify-distilled.ts 2025-10-12 23:57:04 +02:00
Stanislav Khromov
f27a7778c7 wip 2025-10-12 23:49:16 +02:00
Stanislav Khromov
ff64389c68 Update prompts.ts 2025-10-12 23:35:32 +02:00
Stanislav Khromov
ea0624cf41 Update generate-summaries.ts 2025-10-12 23:34:02 +02:00
Stanislav Khromov
4079808b03 Add sorting controls to compare sections sidebar 2025-10-12 23:30:18 +02:00
Stanislav Khromov
43af0ac413 Move commander dependency to devDependencies 2025-10-12 23:13:31 +02:00
Stanislav Khromov
94d56fcaf5 Update +page.server.ts 2025-10-12 23:11:28 +02:00
Stanislav Khromov
4d0c1fdf3d Replace vite-node with node-resolve-ts in scripts 2025-10-12 23:08:55 +02:00
Stanislav Khromov
5ddd4c11a9 Update anthropic.ts 2025-10-12 00:08:32 +02:00
Stanislav Khromov
31cc38e1c1 fix vite-node import 2025-10-12 00:05:36 +02:00
Stanislav Khromov
1fe5383582 Update +page.server.ts 2025-10-12 00:03:53 +02:00
Stanislav Khromov
a22bd3df84 Update get-documentation.ts 2025-10-12 00:01:56 +02:00
Stanislav Khromov
0b846a8ae5 Update +page.svelte 2025-10-11 23:59:49 +02:00
Stanislav Khromov
cac9adf8cb Update +page.server.ts 2025-10-11 23:59:45 +02:00
Stanislav Khromov
9045e618c2 Update distilled.json 2025-10-11 23:40:49 +02:00
Stanislav Khromov
42b2a44b4b Update distilled.json 2025-10-11 23:38:42 +02:00
Stanislav Khromov
71eb1f770f don't restrict max_tokens 2025-10-11 23:31:49 +02:00
Stanislav Khromov
0482033c84 lint 2025-10-11 23:23:23 +02:00
Stanislav Khromov
8d5382d50d wip 2025-10-11 23:22:33 +02:00
Stanislav Khromov
ed1b018260 Update +page.svelte 2025-10-11 23:16:11 +02:00
Stanislav Khromov
9289421cd7 Update eslint.config.js 2025-10-11 23:14:42 +02:00
Stanislav Khromov
60610c6ebf format 2025-10-11 23:08:19 +02:00
Stanislav Khromov
1672ef7f94 Update .cocominify 2025-10-11 23:02:58 +02:00
Stanislav Khromov
34ae757b74 use vite-node for the scripts 2025-10-11 23:02:44 +02:00
Stanislav Khromov
ea9e3f425a Update utils.ts 2025-10-11 22:58:15 +02:00
Stanislav Khromov
3e991787f8 calc space savings 2025-10-11 22:57:56 +02:00
Stanislav Khromov
253f545c4d Update +page.svelte 2025-10-11 22:52:06 +02:00
Stanislav Khromov
d9a45a38b8 Update +page.svelte 2025-10-11 22:48:32 +02:00
Stanislav Khromov
b0d387ff36 wip 2025-10-11 22:48:09 +02:00
Stanislav Khromov
daec272ff8 wip 2025-10-11 22:46:06 +02:00
Stanislav Khromov
f2bda80b06 create visualization 2025-10-11 22:41:37 +02:00
Stanislav Khromov
1c9fc91c21 wip 2025-10-11 22:23:44 +02:00
Stanislav Khromov
24b9351a92 Update use_cases.json 2025-10-11 22:19:33 +02:00
Stanislav Khromov
e70db40d3c remove content hashes and use original content instead 2025-10-11 21:49:38 +02:00
Stanislav Khromov
7053cfcc2d write original content 2025-10-11 21:47:23 +02:00
Stanislav Khromov
2a640ba716 Update generate-summaries.ts 2025-10-11 21:41:02 +02:00
Stanislav Khromov
8273c6cba7 Update prompts.ts 2025-10-11 03:12:13 +02:00
Stanislav Khromov
dc039665d8 Update get-documentation.ts 2025-10-11 03:11:47 +02:00
Stanislav Khromov
9d4bf59b0c Update utils.ts 2025-10-11 03:10:42 +02:00
Stanislav Khromov
e528324785 wip 2025-10-11 03:07:02 +02:00
Stanislav Khromov
bb41d4a4cc Create distilled.json 2025-10-11 02:58:44 +02:00
Stanislav Khromov
24d22979ce Update package.json 2025-10-11 02:56:36 +02:00
Stanislav Khromov
d4fe086517 move prompts 2025-10-11 02:56:12 +02:00
Stanislav Khromov
30dc2eba84 Update generate-summaries.ts 2025-10-11 02:53:09 +02:00
Stanislav Khromov
ae92b84ffa Update generate-summaries.ts 2025-10-11 02:52:27 +02:00
Stanislav Khromov
eecaf8cd5a add distilled support 2025-10-11 02:51:12 +02:00
Stanislav Khromov
f1c72ae73c Update use_cases.json 2025-10-11 02:45:02 +02:00
Stanislav Khromov
02172d68cf remove unnecessary error checking 2025-10-11 02:39:19 +02:00
Stanislav Khromov
efd1d94c01 Update generate-summaries.test.ts 2025-10-11 02:31:53 +02:00
Stanislav Khromov
e347cc8caa Update generate-summaries.ts 2025-10-11 02:25:38 +02:00
Stanislav Khromov
15677b8ca4 wip 2025-10-11 02:22:54 +02:00
Stanislav Khromov
3a8e488a73 wip 2025-10-11 02:18:16 +02:00
Stanislav Khromov
1555c67a0f Update use_cases.json 2025-10-11 02:15:44 +02:00
Stanislav Khromov
4cbe859322 wip 2025-10-11 02:12:36 +02:00
Stanislav Khromov
a100bbb9be Update generate-summaries.ts 2025-10-11 02:08:19 +02:00
Stanislav Khromov
4344e09ce3 remove --sections 2025-10-11 01:53:47 +02:00
Stanislav Khromov
5fc108cf76 wip 2025-10-11 01:48:38 +02:00
Stanislav Khromov
740a08eb35 Update generate-summaries.ts 2025-10-11 01:43:32 +02:00
Stanislav Khromov
d054d48d6b Update generate-summaries.ts 2025-10-11 01:43:03 +02:00
Stanislav Khromov
4cdd419cd9 Create thirty-fans-stay.md 2025-10-11 01:42:02 +02:00
Stanislav Khromov
f1ff4d5862 Update generate-summaries.ts 2025-10-11 01:31:57 +02:00
Stanislav Khromov
6cbc72247e wip 2025-10-11 01:30:29 +02:00
Stanislav Khromov
98be540b18 Update generate-summaries.test.ts 2025-10-11 01:26:43 +02:00
Stanislav Khromov
2082ce91bc wip 2025-10-11 01:24:08 +02:00
Stanislav Khromov
9e2db4a7fa Update generate-summaries.ts 2025-10-11 01:19:35 +02:00
Stanislav Khromov
fe869d7a2c Update generate-summaries.ts 2025-10-11 01:19:10 +02:00
Stanislav Khromov
31ce09551c Update generate-summaries.ts 2025-10-11 01:18:19 +02:00
Stanislav Khromov
20bfbe09e3 wip 2025-10-11 01:18:08 +02:00
Stanislav Khromov
e3794491a4 Update .cocoignore 2025-10-11 01:08:58 +02:00
371 changed files with 20186 additions and 428 deletions

View File

@@ -0,0 +1,5 @@
---
'@sveltejs/mcp': minor
---
Adds LLM-condensed documentation for smaller context usage

View File

@@ -1,3 +1,4 @@
.claude
.github
.vscode
.vscode
documentation/

View File

@@ -1 +1,4 @@
packages/mcp-server/src/use_cases.json
packages/mcp-server/src/use_cases.json
packages/mcp-server/src/mcp/autofixers
packages/mcp-server/src/distilled.json
packages/mcp-server/src/distilled-verification.json

View File

@@ -11,4 +11,7 @@ bun.lockb
/**/.svelte-kit/*
# Claude Code
.claude/
.claude/
# MCP Server Markdown Files
packages/mcp-server/**/*.md

302
CLAUDE.md
View File

@@ -2,72 +2,234 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
## Project Structure
This is a Svelte MCP (Model Context Protocol) server implementation that includes both SvelteKit web interface and MCP server functionality.
This is a monorepo containing the official Svelte MCP (Model Context Protocol) server implementation with multiple packages:
- **`packages/mcp-server/`** - Core MCP server logic with tools, prompts, and autofixers
- **`packages/mcp-stdio/`** - CLI wrapper for running MCP server via stdio transport (`@sveltejs/mcp` on npm)
- **`packages/mcp-schema/`** - Shared database schema definitions using Drizzle ORM
- **`apps/mcp-remote/`** - SvelteKit web application for remote HTTP MCP access and documentation comparison
## Development Commands
### Setup
```bash
pnpm i
cp .env.example .env
# Set the VOYAGE_API_KEY for embeddings support in .env
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
# Set DATABASE_URL, DATABASE_TOKEN, and VOYAGE_API_KEY (optional) in apps/mcp-remote/.env
pnpm dev
```
### Common Commands
- `pnpm dev` - Start SvelteKit development server
- `pnpm build` - Build the application for production
- `pnpm start` - Run the MCP server (Node.js entry point)
- `pnpm check` - Run Svelte type checking
- `pnpm check:watch` - Run type checking in watch mode
- `pnpm dev` - Start SvelteKit development server (apps/mcp-remote)
- `pnpm build` - Build all packages
- `pnpm check` - Run type checking across all packages
- `pnpm lint` - Run prettier check and eslint
- `pnpm lint:fix` - Auto-fix linting issues
- `pnpm format` - Format code with prettier
- `pnpm test` - Run unit tests with vitest
- `pnpm test:watch` - Run tests in watch mode
### Database Commands (Drizzle ORM)
### Database Commands (apps/mcp-remote)
- `pnpm db:push` - Push schema changes to database
- `pnpm db:push` - Push schema changes to Turso database
- `pnpm db:generate` - Generate migration files
- `pnpm db:migrate` - Run migrations
- `pnpm db:studio` - Open Drizzle Studio
### Documentation Generation Commands (packages/mcp-server)
#### Generate Use Case Summaries
Generate short descriptions of when each documentation section would be useful:
- `pnpm generate-summaries` - Generate use case summaries for all sections
- `pnpm generate-summaries:dry-run` - Preview what would be generated without making API calls
- `pnpm generate-summaries:debug` - Process only 2 sections for debugging
#### Generate Distilled Documentation
Generate condensed versions of the documentation to reduce context size:
- `pnpm generate-distilled` - Generate distilled versions for all sections
- `pnpm generate-distilled:dry-run` - Preview what would be generated without making API calls
- `pnpm generate-distilled:debug` - Process only 2 sections for debugging
#### Verify Distilled Documentation
Verify the accuracy of distilled summaries against original documentation:
- `pnpm verify-distilled` - Verify all distilled summaries for accuracy
- `pnpm show-verification-errors` - Display all sections that failed verification
The verification workflow:
1. Run `pnpm verify-distilled` to verify all distilled summaries
- Loads `distilled.json` containing summaries and original content
- Uses the Anthropic Batch API to send each summary and original content to Claude
- Claude evaluates whether the summary is accurate or contains errors/omissions
- Generates `distilled-verification.json` with results (ACCURATE/NOT_ACCURATE) and reasoning
- Outputs statistics about accuracy rates
2. Run `pnpm show-verification-errors` to see detailed list of all sections that are NOT_ACCURATE
- Displays each problematic section with its reasoning
- Shows summary statistics
**Note:** All documentation generation and verification commands require `ANTHROPIC_API_KEY` to be set in `packages/mcp-server/.env`
### Documentation Updates
- `pnpm generate-prompt-docs` - Update documentation/docs/30-capabilities/30-prompts.md based on prompt handlers
### Publishing Commands
- `pnpm release` - Build and publish to npm using changesets
- `pnpm changeset:version` - Update versions and sync package.json with server.json
### Development Tools
- `pnpm inspect` - Launch MCP inspector at http://localhost:6274/ for testing tools and prompts
## Architecture
### MCP Server Implementation
### MCP Server Implementation (`packages/mcp-server/src/mcp/`)
The core MCP server is implemented in `src/lib/mcp/index.ts` using the `tmcp` library with:
The core MCP server is implemented using the `tmcp` library with:
- **Transport Layers**: Both HTTP (`HttpTransport`) and STDIO (`StdioTransport`) support
- **Transport Layers**: Both HTTP (`@tmcp/transport-http`) and STDIO (`@tmcp/transport-stdio`) support
- **Schema Validation**: Uses Valibot with `ValibotJsonSchemaAdapter`
- **Main Tool**: `svelte-autofixer` - analyzes Svelte code and provides suggestions/fixes
- **Server Definition**: `packages/mcp-server/src/mcp/index.ts` exports the configured server instance
### Code Analysis Engine
### MCP Tools (`packages/mcp-server/src/mcp/handlers/tools/`)
Located in `src/lib/server/analyze/`:
#### get-documentation
- **Parser** (`parse.ts`): Uses `svelte-eslint-parser` and TypeScript parser to analyze Svelte components
Retrieves documentation content for Svelte 5 or SvelteKit sections. Supports:
- Single or multiple section names
- Search by title (e.g., "$state") or file path (e.g., "cli/overview")
- Optional distilled versions to optimize context size (default: true)
- Automatically loads distilled content from `distilled.json` when available
#### list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with:
- Section titles and file paths
- "use_cases" metadata describing when each section is useful
- Helps LLMs determine which documentation to fetch
#### svelte-autofixer
Analyzes Svelte component or module code and returns suggestions/fixes:
- Runs compilation checks
- Runs ESLint checks
- Runs custom autofixer visitors
- Requires `code`, `desired_svelte_version` (4 or 5), and optional `filename`
- Returns issues, suggestions, and whether another tool call is needed
#### playground-link
Generates a Svelte Playground link from code snippets:
- Accepts multiple files (must include `App.svelte` as entry point)
- Optional Tailwind CSS support
- Compresses and encodes files into URL hash
### MCP Prompts (`packages/mcp-server/src/mcp/handlers/prompts/`)
#### svelte-task
A prompt template that instructs LLMs on how to:
- Query available documentation sections
- Use the autofixer iteratively until no issues remain
- Generate playground links when appropriate
- Follows best practices for Svelte development
Prompts are defined with:
- `generate_for_docs()` - Function to generate prompt text for documentation
- `docs_description` - Human-readable description
- Prompt handler - Server registration logic with schema and completions
### MCP Resources (`packages/mcp-server/src/mcp/handlers/resources/`)
#### Svelte-Doc-Section
URI template: `svelte://{/slug*}.md`
Lists and fetches individual documentation sections:
- Lists all sections with metadata (title, use_cases, URI)
- Provides completions for slug parameter
- Fetches content from svelte.dev/docs/
### Code Analysis & Parsing (`packages/mcp-server/src/parse/`)
- **Parser** (`parse.ts`): Uses `svelte-eslint-parser` and TypeScript parser
- **Scope Analysis**: Tracks variables, references, and scopes across the AST
- **Rune Detection**: Identifies Svelte 5 runes (`$state`, `$effect`, `$derived`, etc.)
### Autofixer System
### Autofixer System (`packages/mcp-server/src/mcp/autofixers/`)
- **Autofixers** (`src/lib/mcp/autofixers.ts`): Visitor pattern implementations for code analysis
- **Walker Utility** (`src/lib/index.ts`): Enhanced AST walking with visitor mixing capabilities
- **Current Autofixer**: `assign_in_effect` - detects assignments to `$state` variables inside `$effect` blocks
The autofixer system uses a visitor pattern to analyze Svelte components:
### Database Layer
#### Core Autofixer Files
- **ORM**: Drizzle with SQLite backend
- **Schema** (`src/lib/server/db/schema.ts`): Vector table for embeddings support
- **Utils** (`src/lib/server/db/utils.ts`): Custom float32 array type for vectors
- **`add-compile-issues.ts`** - Runs Svelte compiler and adds compilation errors
- **`add-eslint-issues.ts`** - Runs ESLint with svelte-eslint-parser
- **`add-autofixers-issues.ts`** - Orchestrates all custom autofixer visitors
### SvelteKit Integration
#### AST Walking (`ast/`)
- **`walk.ts`** - Enhanced AST walking with visitor mixing capabilities using zimmerframe
- **`utils.ts`** - Utility functions for AST manipulation
#### Autofixer Visitors (`visitors/`)
Each visitor checks for specific issues:
- **`assign-in-effect.ts`** - Detects assignments to `$state` variables inside `$effect` blocks
- **`derived-with-function.ts`** - Suggests using `$derived.by()` when passing a function to `$derived()`
- **`imported-runes.ts`** - Detects attempts to import runes (they're globals)
- **`read-state-with-dollar.ts`** - Detects reading `$state` variables with `$` prefix in Svelte 5
- **`suggest-attachments.ts`** - Suggests attachments API for bind:this and actions
- **`use-runes-instead-of-store.ts`** - Suggests migrating from stores to runes in Svelte 5
- **`wrong-property-access-state.ts`** - Detects incorrect property access patterns on `$state` variables
### Database Layer (`packages/mcp-schema/`)
- **ORM**: Drizzle with LibSQL/Turso backend
- **Schema** (`src/schema.js`):
- `content` - Original documentation with embeddings
- `content_distilled` - Distilled/condensed documentation with embeddings
- `distillations` - Stored distilled documentation versions
- `distillation_jobs` - Batch processing job tracking
- **Utils** (`src/utils.js`): Custom `float_32_array` type for vector embeddings
### SvelteKit Web App (`apps/mcp-remote/`)
Remote HTTP MCP server with documentation comparison interface:
- **Hooks** (`src/hooks.server.ts`): Integrates MCP HTTP transport with SvelteKit requests
- **Routes**: Basic web interface for the MCP server
- **Routes**:
- `/` - Landing page
- `/compare/use_cases` - Compare use case summaries with original docs
- `/compare/distilled` - Compare distilled docs with original docs
- **MCP Setup** (`src/lib/mcp/index.ts`): HTTP transport configuration
### CLI Package (`packages/mcp-stdio/`)
Standalone npm package (`@sveltejs/mcp`) that:
- Runs the MCP server via stdio transport
- Built with tsdown for optimal bundling
- Externalizes `eslint` dependency (required at runtime)
- Published to npm registry and MCP registry
## Key Dependencies
@@ -75,27 +237,87 @@ Located in `src/lib/server/analyze/`:
- **@tmcp/transport-http** & **@tmcp/transport-stdio**: Transport layers
- **@tmcp/adapter-valibot**: Schema validation adapter
- **svelte-eslint-parser**: Svelte component parsing
- **typescript-eslint**: TypeScript AST parsing
- **zimmerframe**: AST walking utilities
- **drizzle-orm**: Database ORM with SQLite
- **drizzle-orm**: Database ORM with LibSQL
- **valibot**: Schema validation library
- **@anthropic-ai/sdk**: Anthropic Batch API for documentation processing
- **tsdown**: TypeScript bundler for CLI package
## Environment Configuration
Required environment variables:
### apps/mcp-remote/.env
- `DATABASE_URL`: SQLite database path (default: `file:test.db`)
- `VOYAGE_API_KEY`: API key for embeddings support (optional)
Required for the remote MCP server:
When connected to the svelte-llm MCP server, you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
- `DATABASE_URL`: LibSQL/Turso database URL (e.g., `libsql://db-name.turso.io`)
- `DATABASE_TOKEN`: Turso authentication token
- `VOYAGE_API_KEY`: API key for embeddings support (optional, for vector search features)
## Available MCP Tools:
### packages/mcp-server/.env
### 1. list-sections
Required for documentation generation scripts:
Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
- `ANTHROPIC_API_KEY`: API key for generating and verifying distilled documentation
### 2. get-documentation
## Using the MCP Server
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.
### Available MCP Tools
1. **list-sections** - ALWAYS call this FIRST to discover available documentation
- Returns structured list with titles, paths, and use_cases metadata
- Use the use_cases field to determine relevant sections
2. **get-documentation** - Retrieves documentation content
- Accepts single section name or array of section names
- Searches by title or file path
- Optional `use_distilled` parameter (default: true) for condensed versions
- After calling list-sections, fetch ALL relevant sections at once
3. **svelte-autofixer** - Analyzes Svelte code
- MUST be used whenever writing Svelte code before returning to user
- Keep calling until no issues/suggestions remain
- Provides compilation errors, ESLint issues, and custom suggestions
4. **playground-link** - Generates Svelte Playground URLs
- Only use after code is finalized and user confirms
- Requires App.svelte as entry point
- Can include multiple files
### Available MCP Prompts
- **svelte-task** - Use for any Svelte-related task
- Instructs LLM on proper tool usage
- Enforces iterative autofixer workflow
- Guides documentation querying
## Constants & Configuration
### Svelte Runes (`packages/mcp-server/src/constants.ts`)
Base runes:
- `$state`, `$effect`, `$derived`, `$inspect`, `$props`, `$bindable`, `$host`
Nested runes:
- `$state.raw`, `$state.snapshot`, `$effect.pre`, `$effect.tracking`, `$effect.pending`, `$effect.root`, `$derived.by`, `$inspect.trace`, `$props.id`
## Code Style & Standards
- **Naming**: Use `snake_case` for variables and functions
- **Types**: Prefer TypeScript type imports with JSDoc where needed
- **Formatting**: Tabs for indentation, single quotes, trailing commas
- **File Extensions**: Include `.js` extension in imports even for TypeScript files
- **Linting**: Run `pnpm lint:fix` before committing
## Testing
- Unit tests use Vitest
- Test files use `.test.ts` or `.spec.ts` suffix
- Run `pnpm test` to execute all tests
- Run `pnpm test:watch` for watch mode
- Test coverage includes:
- Documentation generation and verification
- Autofixer visitors
- Parsing and AST analysis

View File

@@ -7,7 +7,10 @@ export async function handle({ event, resolve }) {
const accept = event.request.headers.get('accept');
if (accept) {
const accepts = accept.split(',');
if (!accepts.includes('text/event-stream')) {
if (
!accepts.includes('text/event-stream') &&
(event.url.pathname.startsWith('/mcp') || event.url.pathname === '/')
) {
// the request it's a browser request, not an MCP client request
// it means someone probably opened it from the docs...we should redirect to the docs
redirect(302, 'https://svelte.dev/docs/mcp/overview');

View File

@@ -1 +0,0 @@
<h1>Official Svelte MCP</h1>

View File

@@ -0,0 +1,73 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import distilled_data from '@sveltejs/mcp-server/distilled.json';
import use_cases_data from '@sveltejs/mcp-server/use-cases.json';
interface SummaryData {
generated_at: string;
model: string;
total_sections: number;
successful_summaries: number;
summaries: Record<string, string>;
content: Record<string, string>;
}
const VALID_TYPES = ['use_cases', 'distilled'] as const;
type ValidType = (typeof VALID_TYPES)[number];
function is_valid_type(type: string): type is ValidType {
return VALID_TYPES.includes(type as ValidType);
}
export const load: PageServerLoad = async ({ params }) => {
const { type } = params;
if (!is_valid_type(type)) {
throw error(404, 'Comparison type not found');
}
const data = (type === 'use_cases' ? use_cases_data : distilled_data) as SummaryData;
const sections = Object.keys(data.summaries).map((slug) => {
const summary = data.summaries[slug] || '';
const content = data.content[slug] || '';
const original_length = content.length;
const distilled_length = summary.length;
const space_savings =
original_length > 0 ? ((original_length - distilled_length) / original_length) * 100 : 0;
return {
slug,
summary,
content,
original_length,
distilled_length,
space_savings,
};
});
const total_original_length = sections.reduce((sum, s) => sum + s.original_length, 0);
const total_distilled_length = sections.reduce((sum, s) => sum + s.distilled_length, 0);
const total_space_savings =
total_original_length > 0
? ((total_original_length - total_distilled_length) / total_original_length) * 100
: 0;
const title =
type === 'use_cases' ? 'Use Cases Comparison' : 'Distilled Documentation Comparison';
return {
type,
title,
metadata: {
generated_at: data.generated_at,
model: data.model,
total_sections: data.total_sections,
successful_summaries: data.successful_summaries,
total_original_length,
total_distilled_length,
total_space_savings,
},
sections,
};
};

View File

@@ -0,0 +1,427 @@
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let selected_slug = $state<string | null>(null);
let search_query = $state('');
let sort_order = $state<'default' | 'largest' | 'smallest'>('default');
let filtered_sections = $derived.by(() => {
// First, filter sections
const filtered = data.sections.filter(
(section) =>
section.slug.toLowerCase().includes(search_query.toLowerCase()) ||
section.summary.toLowerCase().includes(search_query.toLowerCase()),
);
// Then, sort based on selected sort order
if (sort_order === 'largest') {
return [...filtered].sort((a, b) => b.space_savings - a.space_savings);
} else if (sort_order === 'smallest') {
return [...filtered].sort((a, b) => a.space_savings - b.space_savings);
}
return filtered;
});
let selected_section = $derived(
selected_slug ? data.sections.find((s) => s.slug === selected_slug) : null,
);
let summary_title = $derived(
data.type === 'use_cases' ? 'Use Cases Summary' : 'Distilled Version',
);
let is_distilled = $derived(data.type === 'distilled');
function select_section(slug: string) {
selected_slug = selected_slug === slug ? null : slug;
}
</script>
<svelte:head>
<title>{data.title}</title>
</svelte:head>
<div class="container">
<header>
<h1>{data.title}</h1>
<div class="metadata">
<span>Generated: {new Date(data.metadata.generated_at).toLocaleString()}</span>
<span>Model: {data.metadata.model}</span>
<span>Sections: {data.metadata.successful_summaries}/{data.metadata.total_sections}</span>
<span
>Space Savings: {data.metadata.total_distilled_length.toLocaleString()}/{data.metadata.total_original_length.toLocaleString()}
chars ({data.metadata.total_space_savings.toFixed(1)}% reduction)</span
>
</div>
</header>
<div class="main-content">
<aside class="sidebar">
<div class="sort-controls">
<span class="sort-label">Sort by:</span>
<div class="sort-buttons">
<button
class="sort-button"
class:active={sort_order === 'default'}
onclick={() => (sort_order = 'default')}
>
Default
</button>
<button
class="sort-button"
class:active={sort_order === 'largest'}
onclick={() => (sort_order = 'largest')}
>
Largest Reduction
</button>
<button
class="sort-button"
class:active={sort_order === 'smallest'}
onclick={() => (sort_order = 'smallest')}
>
Smallest Reduction
</button>
</div>
</div>
<div class="search-box">
<input
type="search"
bind:value={search_query}
placeholder="Search sections..."
class="search-input"
/>
</div>
<ul class="section-list">
{#each filtered_sections as section (section.slug)}
<li>
<button
class="section-item"
class:active={selected_slug === section.slug}
onclick={() => select_section(section.slug)}
>
<div class="section-header">
<div class="section-title">{section.slug}</div>
<div class="section-savings" class:negative={section.space_savings < 0}>
{section.space_savings.toFixed(1)}%
</div>
</div>
<div class="section-preview">
{is_distilled
? section.summary.slice(0, 100) + (section.summary.length > 100 ? '...' : '')
: section.summary}
</div>
</button>
</li>
{/each}
</ul>
</aside>
<main class="comparison-view">
{#if selected_section}
<div class="comparison-grid">
<div class="comparison-column">
<h2>
Original Content
<span class="char-count"
>{selected_section.original_length.toLocaleString()} chars</span
>
</h2>
<div class="content-box">
<pre>{selected_section.content}</pre>
</div>
</div>
<div class="comparison-column">
<h2>
{summary_title}
<span class="char-count"
>{selected_section.distilled_length.toLocaleString()} chars ({selected_section.space_savings.toFixed(
1,
)}% savings)</span
>
</h2>
<div class="content-box">
<pre>{selected_section.summary}</pre>
</div>
</div>
</div>
{:else}
<div class="empty-state">
<p>Select a section from the list to view the comparison</p>
</div>
{/if}
</main>
</div>
</div>
<style>
.container {
max-width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
header {
background: white;
border-bottom: 1px solid #e0e0e0;
padding: 1.5rem 2rem;
}
h1 {
margin: 0 0 0.5rem 0;
font-size: 1.875rem;
font-weight: 600;
color: #1a1a1a;
}
.metadata {
display: flex;
gap: 1.5rem;
font-size: 0.9375rem;
color: #666;
flex-wrap: wrap;
}
.main-content {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: 350px;
background: white;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
}
.sort-controls {
padding: 1rem;
border-bottom: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.sort-label {
font-size: 0.8125rem;
font-weight: 500;
color: #666;
}
.sort-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.sort-button {
padding: 0.375rem 0.75rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
background: white;
font-size: 0.8125rem;
font-family: inherit;
cursor: pointer;
transition: all 0.15s;
color: #666;
}
.sort-button:hover {
background: #f9fafb;
border-color: #d0d0d0;
}
.sort-button.active {
background: #3b82f6;
border-color: #3b82f6;
color: white;
}
.search-box {
padding: 1rem;
border-bottom: 1px solid #e0e0e0;
}
.search-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 0.9375rem;
font-family: inherit;
}
.search-input:focus {
outline: none;
border-color: #3b82f6;
}
.section-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
flex: 1;
}
.section-item {
width: 100%;
text-align: left;
padding: 0.75rem 1rem;
border: none;
border-bottom: 1px solid #f0f0f0;
background: white;
cursor: pointer;
transition: background-color 0.15s;
font-family: inherit;
}
.section-item:hover {
background: #f9fafb;
}
.section-item.active {
background: #eff6ff;
border-left: 3px solid #3b82f6;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.section-title {
font-size: 0.9375rem;
font-weight: 500;
color: #1a1a1a;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.section-savings {
font-size: 0.75rem;
font-weight: 600;
color: #10b981;
padding: 0.125rem 0.375rem;
background: #d1fae5;
border-radius: 3px;
white-space: nowrap;
}
.section-savings.negative {
color: #ef4444;
background: #fee2e2;
}
.section-preview {
font-size: 0.8125rem;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.comparison-view {
flex: 1;
overflow: auto;
background: #fafafa;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding: 1rem;
height: 100%;
}
.comparison-column {
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.comparison-column h2 {
margin: 0;
padding: 1rem;
font-size: 1.125rem;
font-weight: 600;
color: #1a1a1a;
border-bottom: 1px solid #e0e0e0;
background: #fafafa;
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.char-count {
font-size: 0.8125rem;
font-weight: 400;
color: #666;
white-space: nowrap;
}
.content-box {
padding: 1rem;
overflow: auto;
flex: 1;
font-size: 0.9375rem;
line-height: 1.6;
}
.content-box pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Courier New', monospace;
font-size: 0.875rem;
color: #333;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
font-size: 0.9375rem;
}
@media (max-width: 768px) {
.comparison-grid {
grid-template-columns: 1fr;
}
.sidebar {
width: 100%;
max-height: 25vh;
}
.main-content {
flex-direction: column;
}
.comparison-column h2 {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.char-count {
font-size: 0.75rem;
}
}
</style>

View File

@@ -57,6 +57,7 @@ export default /** @type {import("eslint").Linter.Config} */ ([
svelte: 'always',
},
],
'svelte/no-navigation-without-resolve': 'off',
},
},
{

View File

@@ -17,8 +17,15 @@
"test:watch": "npm run test:unit -- --watch",
"inspect": "pnpm mcp-inspector",
"generate-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries",
"generate-summaries:dry-run": "pnpm --filter @sveltejs/mcp-server run generate-summaries:dry-run",
"generate-summaries:debug": "pnpm --filter @sveltejs/mcp-server run generate-summaries:debug",
"generate-distilled": "pnpm --filter @sveltejs/mcp-server run generate-distilled",
"generate-distilled:dry-run": "pnpm --filter @sveltejs/mcp-server run generate-distilled:dry-run",
"generate-distilled:debug": "pnpm --filter @sveltejs/mcp-server run generate-distilled:debug",
"verify-distilled": "pnpm --filter @sveltejs/mcp-server run verify-distilled",
"show-verification-errors": "pnpm --filter @sveltejs/mcp-server run show-verification-errors",
"export-summaries": "pnpm --filter @sveltejs/mcp-server run export-summaries",
"generate-prompt-docs": "node --import node-resolve-ts/register scripts/update-docs-prompts.ts",
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all"
},
@@ -46,6 +53,7 @@
"publint": "^0.3.13",
"typescript": "^5.0.0",
"typescript-eslint": "^8.44.1",
"vite-node": "^3.2.4",
"vitest": "^3.2.3"
},
"pnpm": {

View File

@@ -10,11 +10,20 @@
"type": "module",
"scripts": {
"test": "vitest",
"generate-summaries": "node scripts/generate-summaries.ts --experimental-strip-types",
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
"generate-summaries": "node --import node-resolve-ts/register scripts/generate-summaries.ts && prettier --write --log-level silent 'src/**/*.{js,ts,json}'",
"generate-summaries:dry-run": "node --import node-resolve-ts/register scripts/generate-summaries.ts --dry-run && prettier --write --log-level silent 'src/**/*.{js,ts,json}'",
"generate-summaries:debug": "DEBUG_MODE=1 node --import node-resolve-ts/register scripts/generate-summaries.ts && prettier --write --log-level silent 'src/**/*.{js,ts,json}'",
"generate-distilled": "node --import node-resolve-ts/register scripts/generate-summaries.ts --prompt-type distilled && prettier --write --log-level silent 'src/**/*.{js,ts,json}'",
"generate-distilled:dry-run": "node --import node-resolve-ts/register scripts/generate-summaries.ts --prompt-type distilled --dry-run && prettier --write --log-level silent 'src/**/*.{js,ts,json}'",
"generate-distilled:debug": "DEBUG_MODE=1 node --import node-resolve-ts/register scripts/generate-summaries.ts --prompt-type distilled && prettier --write --log-level silent 'src/**/*.{js,ts,json}'",
"verify-distilled": "node --import node-resolve-ts/register scripts/verify-distilled.ts",
"show-verification-errors": "node --import node-resolve-ts/register scripts/show-verification-errors.ts",
"export-summaries": "node --import node-resolve-ts/register scripts/export-summaries.ts && prettier --write --log-level silent 'src/**/*.{js,ts,json}'"
},
"exports": {
".": "./src/index.ts"
".": "./src/index.ts",
"./distilled.json": "./src/distilled.json",
"./use-cases.json": "./src/use_cases.json"
},
"peerDependencies": {
"drizzle-orm": "^0.44.0"
@@ -40,6 +49,7 @@
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@typescript-eslint/types": "^8.44.0",
"commander": "^13.1.0",
"dotenv": "^17.2.3"
}
}

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env node
import 'dotenv/config';
import { readFile, access } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { Command } from 'commander';
import * as v from 'valibot';
import { summary_data_schema, type SummaryData } from '../src/lib/schemas.ts';
import { export_summaries_to_markdown } from './lib/export-markdown.ts';
interface CliOptions {
type: 'use_cases' | 'distilled' | 'both';
}
const current_filename = fileURLToPath(import.meta.url);
const current_dirname = path.dirname(current_filename);
const program = new Command();
program
.name('export-summaries')
.description('Export summaries from JSON files to individual markdown files')
.version('1.0.0')
.option('-t, --type <type>', 'Type to export: "use_cases", "distilled", or "both"', 'both');
async function read_file_as_string(
file_path: string,
encoding: BufferEncoding = 'utf-8',
): Promise<string> {
const content = await readFile(file_path, encoding);
return v.parse(v.string(), content);
}
async function load_summaries_json(output_path: string): Promise<SummaryData | null> {
try {
await access(output_path);
} catch {
return null;
}
const content = await read_file_as_string(output_path, 'utf-8');
const data = JSON.parse(content);
const validated = v.safeParse(summary_data_schema, data);
if (!validated.success) {
throw new Error(
`File has invalid schema: ${output_path}\n` +
`Validation errors: ${JSON.stringify(validated.issues, null, 2)}`,
);
}
return validated.output;
}
async function export_summaries_for_type(type: 'use_cases' | 'distilled'): Promise<void> {
const json_filename = type === 'distilled' ? 'distilled.json' : 'use_cases.json';
const input_path = path.join(current_dirname, `../src/${json_filename}`);
console.log(`\n📁 Processing ${type.toUpperCase()}...`);
console.log(` Input: ${json_filename}`);
// Load the JSON file
console.log(` 📂 Loading ${json_filename}...`);
const data = await load_summaries_json(input_path);
if (!data) {
console.error(` ❌ Error: Could not load ${json_filename}`);
return;
}
const summaries = data.summaries;
console.log(` ✅ Found ${Object.keys(summaries).length} summaries`);
// Use shared export function
await export_summaries_to_markdown(summaries, type, path.join(current_dirname, '../summaries'));
}
async function main() {
program.parse();
const options = program.opts<CliOptions>();
console.log('🚀 Starting summary export...');
// Validate type option
if (!['use_cases', 'distilled', 'both'].includes(options.type)) {
console.error('❌ Error: --type must be "use_cases", "distilled", or "both"');
process.exit(1);
}
// Export based on type option
if (options.type === 'both') {
await export_summaries_for_type('use_cases');
await export_summaries_for_type('distilled');
} else {
await export_summaries_for_type(options.type);
}
console.log('\n✅ Export complete!');
console.log(
'\n📊 Summary files have been written to:',
path.join(current_dirname, '../summaries/'),
);
}
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});

View File

@@ -1,58 +1,59 @@
#!/usr/bin/env node
import 'dotenv/config';
import { writeFile, mkdir } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { writeFile, mkdir, readFile, access } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { Command } from 'commander';
import { get_sections } from '../src/mcp/utils.ts';
import { AnthropicProvider } from '../src/lib/anthropic.ts';
import { type AnthropicBatchRequest, type SummaryData } from '../src/lib/schemas.ts';
import {
type AnthropicBatchRequest,
type SummaryData,
summary_data_schema,
} from '../src/lib/schemas.ts';
import * as v from 'valibot';
import { DISTILLED_PROMPT, USE_CASES_PROMPT } from '../src/mcp/handlers/tools/prompts.ts';
import { export_summaries_to_markdown } from './lib/export-markdown.ts';
interface CliOptions {
force: boolean;
dryRun: boolean;
debug: boolean;
promptType: 'use-cases' | 'distilled';
}
interface SectionChange {
slug: string;
title: string;
url: string;
change_type: 'new' | 'changed' | 'removed';
}
const current_filename = fileURLToPath(import.meta.url);
const current_dirname = path.dirname(current_filename);
const USE_CASES_PROMPT = `
You are tasked with analyzing Svelte 5 and SvelteKit documentation pages to identify when they would be useful.
async function read_file_as_string(
file_path: string,
encoding: BufferEncoding = 'utf-8',
): Promise<string> {
const content = await readFile(file_path, encoding);
return v.parse(v.string(), content);
}
Your task:
1. Read the documentation page content provided
2. Identify the main use cases, scenarios, or queries where this documentation would be relevant
3. Create a VERY SHORT, comma-separated list of use cases (maximum 200 characters total)
4. Think about what a developer might be trying to build or accomplish when they need this documentation
const program = new Command();
Guidelines:
- Focus on WHEN this documentation would be needed, not WHAT it contains
- Consider specific project types (e.g., "e-commerce site", "blog", "dashboard", "social media app")
- Consider specific features (e.g., "authentication", "forms", "data fetching", "animations")
- Consider specific components (e.g., "slider", "modal", "dropdown", "card")
- Consider development stages (e.g., "project setup", "deployment", "testing", "migration")
- Use "always" for fundamental concepts that apply to virtually all Svelte projects
- Be concise but specific
- Use lowercase
- Separate multiple use cases with commas
Examples of good use_cases:
- "always, any svelte project, core reactivity"
- "authentication, login systems, user management"
- "e-commerce, product listings, shopping carts"
- "forms, user input, data submission"
- "deployment, production builds, hosting setup"
- "animation, transitions, interactive ui"
- "routing, navigation, multi-page apps"
- "blog, content sites, markdown rendering"
Requirements:
- Maximum 200 characters (including spaces and commas)
- Lowercase only
- Comma-separated list of use cases
- Focus on WHEN/WHY someone would need this, not what it is
- Be specific about project types, features, or components when applicable
- Use "always" sparingly, only for truly universal concepts
- Do not include quotes or special formatting in your response
- Respond with ONLY the use cases text, no additional text
Here is the documentation page content to analyze:
`;
program
.name('generate-summaries')
.description('Generate use case summaries for Svelte documentation sections')
.version('1.0.0')
.option('-f, --force', 'Force regeneration of all summaries', false)
.option('-d, --dry-run', 'Show what would be changed without making API calls', false)
.option('--debug', 'Debug mode: process only 2 sections', false)
.option(
'-p, --prompt-type <type>',
'Prompt type to use: "use-cases" or "distilled"',
'use-cases',
);
async function fetch_section_content(url: string) {
const response = await fetch(url, { signal: AbortSignal.timeout(30000) });
@@ -62,180 +63,382 @@ async function fetch_section_content(url: string) {
return await response.text();
}
export async function load_existing_summaries(output_path: string): Promise<SummaryData | null> {
try {
await access(output_path);
} catch {
return null;
}
const content = await read_file_as_string(output_path, 'utf-8');
const data = JSON.parse(content);
const validated = v.safeParse(summary_data_schema, data);
if (!validated.success) {
throw new Error(
`Existing file has invalid schema. Please fix or delete the file at: ${output_path}\n` +
`Validation errors: ${JSON.stringify(validated.issues, null, 2)}`,
);
}
return validated.output;
}
function detect_changes(
current_sections: Array<{ slug: string; title: string; url: string }>,
existing_data: SummaryData | null,
current_content: Map<string, string>,
force: boolean,
): {
to_process: Array<{ slug: string; title: string; url: string }>;
to_remove: string[];
changes: SectionChange[];
} {
if (!existing_data || force) {
// First run or force regeneration
return {
to_process: current_sections,
to_remove: [],
changes: current_sections.map((s) => ({
...s,
change_type: force ? 'changed' : 'new',
})),
};
}
const existing_summaries = existing_data.summaries;
const existing_content = existing_data.content;
const existing_slugs = new Set(Object.keys(existing_summaries));
const current_slugs = new Set(current_sections.map((s) => s.slug));
const sections_to_process: typeof current_sections = [];
const changes: SectionChange[] = [];
// Check for new and changed sections
for (const section of current_sections) {
const content = current_content.get(section.slug);
if (!content) {
throw new Error(`No content found for section: ${section.slug}`);
}
if (!existing_slugs.has(section.slug)) {
// New section
sections_to_process.push(section);
changes.push({ ...section, change_type: 'new' });
} else {
// Existing section - check if content changed
const stored_content = existing_content[section.slug] ?? '';
if (content !== stored_content) {
// Content has changed
sections_to_process.push(section);
changes.push({ ...section, change_type: 'changed' });
}
}
}
// Find removed sections
const removed_slugs: string[] = [];
for (const slug of existing_slugs) {
if (!current_slugs.has(slug)) {
removed_slugs.push(slug);
changes.push({
slug,
title: '<removed>',
url: '',
change_type: 'removed',
});
}
}
return {
to_process: sections_to_process,
to_remove: removed_slugs,
changes,
};
}
async function main() {
program.parse();
const options = program.opts<CliOptions>();
// Allow DEBUG_MODE env var as well
const debug = options.debug || process.env.DEBUG_MODE === '1';
console.log('🚀 Starting use cases generation...');
// Check for API key
const api_key = process.env.ANTHROPIC_API_KEY;
if (!api_key) {
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
console.error('Please set it in packages/mcp-server/.env file or export it:');
console.error('export ANTHROPIC_API_KEY=your_api_key_here');
process.exit(1);
// Determine output file based on prompt type
const output_filename = options.promptType === 'distilled' ? 'distilled.json' : 'use_cases.json';
const output_path = path.join(current_dirname, `../src/${output_filename}`);
// Select prompt based on prompt type
const selected_prompt = options.promptType === 'distilled' ? DISTILLED_PROMPT : USE_CASES_PROMPT;
// Display mode information
console.log(`📝 PROMPT MODE: ${options.promptType.toUpperCase()}${output_filename}\n`);
if (options.dryRun) {
console.log('🔍 DRY RUN MODE - No API calls will be made\n');
}
if (options.force) {
console.log('⚡ FORCE MODE - Regenerating all summaries\n');
}
if (debug) {
console.log('🐛 DEBUG MODE - Will process only 2 sections\n');
}
// Load existing summaries
console.log('📂 Loading existing summaries...');
const existing_data = await load_existing_summaries(output_path);
if (existing_data) {
console.log(
`✅ Found existing summaries with ${Object.keys(existing_data.summaries).length} entries`,
);
} else {
console.log('📝 No existing summaries found - will process all sections');
}
// Get all sections
console.log('📚 Fetching documentation sections...');
let sections = await get_sections();
console.log(`Found ${sections.length} sections`);
const all_sections = await get_sections();
console.log(`Found ${all_sections.length} sections from API`);
// Debug mode: limit to 2 sections
const debug_mode = process.env.DEBUG_MODE === '1';
if (debug_mode) {
console.log('🐛 DEBUG_MODE enabled - processing only 2 sections');
sections = sections.slice(0, 2);
// Download content for ALL sections (needed to compute hashes)
console.log('\n📥 Downloading section content...');
const section_content = new Map<string, string>();
for (let i = 0; i < all_sections.length; i++) {
const section = all_sections[i]!;
console.log(` Fetching ${i + 1}/${all_sections.length}: ${section.title}`);
const content = await fetch_section_content(section.url);
section_content.set(section.slug, content);
}
// Fetch content for each section
console.log('📥 Downloading section content...');
console.log(`✅ Successfully downloaded ${section_content.size} sections`);
// Detect what needs to be processed
console.log('\n🔍 Checking for content changes...');
const { to_process, to_remove, changes } = detect_changes(
all_sections,
existing_data,
section_content,
options.force,
);
// Display changes
console.log('\n📊 Change Summary:');
const new_count = changes.filter((c) => c.change_type === 'new').length;
const changed_count = changes.filter((c) => c.change_type === 'changed').length;
const removed_count = changes.filter((c) => c.change_type === 'removed').length;
console.log(` ✨ New sections: ${new_count}`);
console.log(` 🔄 Changed sections: ${changed_count}`);
console.log(` ❌ Removed sections: ${removed_count}`);
console.log(` 📝 To process: ${to_process.length}`);
if (changes.length > 0) {
console.log('\n📋 Detailed changes:');
for (const change of changes) {
const emoji =
change.change_type === 'new' ? ' ✨' : change.change_type === 'changed' ? ' 🔄' : ' ❌';
console.log(`${emoji} [${change.change_type.toUpperCase()}] ${change.slug}`);
}
}
// Exit early if nothing to do
if (to_process.length === 0 && to_remove.length === 0) {
console.log('\n✅ No changes detected - everything is up to date!');
return;
}
// Debug mode: limit sections
let sections_to_process = to_process;
if (debug) {
console.log('\n🐛 Processing only 2 sections for debugging');
sections_to_process = to_process.slice(0, 2);
}
// Dry run mode: exit before API calls
if (options.dryRun) {
console.log('\n🔍 DRY RUN complete - no changes were made');
console.log(`Would have processed ${sections_to_process.length} sections`);
console.log(`Would have removed ${to_remove.length} sections`);
return;
}
// Process with Anthropic API if we have sections to process
const new_summaries: Record<string, string> = {};
const sections_with_content: Array<{
section: (typeof sections)[number];
section: (typeof sections_to_process)[number];
content: string;
index: number;
}> = [];
const download_errors: Array<{ section: string; error: string }> = [];
for (let i = 0; i < sections.length; i++) {
const section = sections[i]!;
try {
console.log(`Fetching ${i + 1}/${sections.length}: ${section.title}`);
const content = await fetch_section_content(section.url);
if (sections_to_process.length === 0) {
console.log('\n📦 Only removing old sections, no API calls needed');
} else {
// Check for API key
const api_key = process.env.ANTHROPIC_API_KEY;
if (!api_key) {
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
console.error('Please set it in packages/mcp-server/.env file or export it:');
console.error('export ANTHROPIC_API_KEY=your_api_key_here');
process.exit(1);
}
// Build sections_with_content from already-downloaded content
console.log('\n📦 Preparing sections for processing...');
for (let i = 0; i < sections_to_process.length; i++) {
const section = sections_to_process[i]!;
const content = section_content.get(section.slug);
if (!content) {
throw new Error(`No content available for ${section.title}`);
}
sections_with_content.push({
section,
content,
index: i,
});
} catch (error) {
const error_msg = error instanceof Error ? error.message : String(error);
console.error(`⚠️ Failed to fetch ${section.title}:`, error_msg);
download_errors.push({ section: section.title, error: error_msg });
}
}
console.log(`✅ Successfully downloaded ${sections_with_content.length} sections`);
if (sections_with_content.length === 0) {
console.error('❌ No sections were successfully downloaded');
process.exit(1);
}
// Initialize Anthropic client
console.log('🤖 Initializing Anthropic API...');
const anthropic = new AnthropicProvider('claude-sonnet-4-5-20250929', api_key);
// Prepare batch requests
console.log('📦 Preparing batch requests...');
const batch_requests: AnthropicBatchRequest[] = sections_with_content.map(
({ content, index }) => ({
custom_id: `section-${index}`,
params: {
model: anthropic.get_model_identifier(),
max_tokens: 250,
messages: [
{
role: 'user',
content: USE_CASES_PROMPT + content,
},
],
temperature: 0,
},
}),
);
// Create and process batch
console.log('🚀 Creating batch job...');
const batch_response = await anthropic.create_batch(batch_requests);
console.log(`✅ Batch created with ID: ${batch_response.id}`);
// Poll for completion
console.log('⏳ Waiting for batch to complete...');
let batch_status = await anthropic.get_batch_status(batch_response.id);
while (batch_status.processing_status === 'in_progress') {
const { succeeded, processing, errored } = batch_status.request_counts;
console.log(` Progress: ${succeeded} succeeded, ${processing} processing, ${errored} errored`);
await new Promise((resolve) => setTimeout(resolve, 5000));
batch_status = await anthropic.get_batch_status(batch_response.id);
}
console.log('✅ Batch processing completed!');
// Get results
if (!batch_status.results_url) {
throw new Error('Batch completed but no results URL available');
}
console.log('📥 Downloading results...');
const results = await anthropic.get_batch_results(batch_status.results_url);
// Process results
console.log('📊 Processing results...');
const summaries: Record<string, string> = {};
const errors: Array<{ section: string; error: string }> = [];
for (const result of results) {
const index = parseInt(result.custom_id.split('-')[1] ?? '0');
const section_data = sections_with_content.find((s) => s.index === index);
if (!section_data) {
console.warn(`⚠️ Could not find section for index ${index}`);
continue;
}
const { section } = section_data;
console.log(`✅ Prepared ${sections_with_content.length} sections for processing`);
if (result.result.type !== 'succeeded' || !result.result.message) {
const error_msg = result.result.error?.message || 'Failed or no message';
console.error(`${section.title}: ${error_msg}`);
errors.push({ section: section.title, error: error_msg });
continue;
console.log('\n🤖 Initializing Anthropic API...');
const anthropic = new AnthropicProvider('claude-sonnet-4-5-20250929', api_key);
// Prepare batch requests
console.log('📦 Preparing batch requests...');
const batch_requests: AnthropicBatchRequest[] = sections_with_content.map(
({ content, index }) => ({
custom_id: `section-${index}`,
params: {
model: anthropic.get_model_identifier(),
max_tokens: 8192,
messages: [
{
role: 'user',
content:
`<instructions>${selected_prompt}</instructions>` +
`<documentation>${content}</documentation>`,
},
],
temperature: 0,
},
}),
);
// Create and process batch
console.log('🚀 Creating batch job...');
const batch_response = await anthropic.create_batch(batch_requests);
console.log(`✅ Batch created with ID: ${batch_response.id}`);
// Poll for completion
console.log('⏳ Waiting for batch to complete...');
let batch_status = await anthropic.get_batch_status(batch_response.id);
while (batch_status.processing_status === 'in_progress') {
const { succeeded, processing, errored } = batch_status.request_counts;
console.log(
` Progress: ${succeeded} succeeded, ${processing} processing, ${errored} errored`,
);
await new Promise((resolve) => setTimeout(resolve, 5000));
batch_status = await anthropic.get_batch_status(batch_response.id);
}
const output_content = result.result.message.content[0]?.text;
if (output_content) {
summaries[section.slug] = output_content.trim();
console.log('✅ Batch processing completed!');
// Get results
if (!batch_status.results_url) {
throw new Error('Batch completed but no results URL available');
}
console.log('📥 Downloading results...');
const results = await anthropic.get_batch_results(batch_status.results_url);
// Process results
console.log('📊 Processing results...');
for (const result of results) {
const index = parseInt(result.custom_id.split('-')[1] ?? '0');
const section_data = sections_with_content.find((s) => s.index === index);
if (!section_data) {
throw new Error(`Could not find section for index ${index}`);
}
const { section } = section_data;
if (result.result.type !== 'succeeded' || !result.result.message) {
const error_msg = result.result.error?.message || 'Failed or no message';
throw new Error(`Failed to generate summary for ${section.title}: ${error_msg}`);
}
const output_content = result.result.message.content[0]?.text;
if (!output_content) {
throw new Error(`No text content in result for ${section.title}`);
}
new_summaries[section.slug] = output_content.trim();
console.log(`${section.title}`);
}
// Merge with existing summaries
console.log('\n📦 Merging results...');
}
// Write output to JSON file
console.log('💾 Writing results to file...');
const output_path = path.join(current_dirname, '../src/use_cases.json');
const output_dir = path.dirname(output_path);
// Start with existing summaries or empty object
const merged_summaries: Record<string, string> = existing_data
? { ...existing_data.summaries }
: {};
// Add/update new summaries
Object.assign(merged_summaries, new_summaries);
// Remove deleted sections from summaries
for (const slug of to_remove) {
delete merged_summaries[slug];
console.log(` 🗑️ Removed: ${slug}`);
}
// Write output
console.log('\n💾 Writing results to file...');
const output_dir = path.dirname(output_path);
await mkdir(output_dir, { recursive: true });
const summary_data: SummaryData = {
generated_at: new Date().toISOString(),
model: anthropic.get_model_identifier(),
total_sections: sections.length,
successful_summaries: Object.keys(summaries).length,
failed_summaries: errors.length,
summaries,
errors: errors.length > 0 ? errors : undefined,
download_errors: download_errors.length > 0 ? download_errors : undefined,
model: 'claude-sonnet-4-5-20250929',
total_sections: all_sections.length,
successful_summaries: Object.keys(merged_summaries).length,
summaries: merged_summaries,
content: Object.fromEntries(section_content),
};
await writeFile(output_path, JSON.stringify(summary_data, null, 2), 'utf-8');
// Export summaries to markdown files
console.log('\n📁 Exporting summaries to markdown files...');
const markdown_type = options.promptType === 'distilled' ? 'distilled' : 'use_cases';
const markdown_files_created = await export_summaries_to_markdown(
merged_summaries,
markdown_type,
path.join(current_dirname, '../summaries'),
);
// Print summary
console.log('\n📊 Summary:');
console.log(` Total sections: ${sections.length}`);
console.log(` Successfully downloaded: ${sections_with_content.length}`);
console.log(` Download failures: ${download_errors.length}`);
console.log(` Successfully analyzed: ${Object.keys(summaries).length}`);
console.log(` Analysis failures: ${errors.length}`);
console.log('\n📊 Final Summary:');
console.log(` Total sections in API: ${all_sections.length}`);
console.log(` Sections processed: ${sections_with_content.length}`);
console.log(` New summaries generated: ${Object.keys(new_summaries).length}`);
console.log(` Sections removed: ${to_remove.length}`);
console.log(` Total summaries in file: ${Object.keys(merged_summaries).length}`);
console.log(` Markdown files created: ${markdown_files_created}`);
console.log(`\n✅ Results written to: ${output_path}`);
if (download_errors.length > 0) {
console.log('\n⚠ Some sections failed to download:');
download_errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`));
}
if (errors.length > 0) {
console.log('\n⚠ Some sections failed to analyze:');
errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`));
}
console.log(
`✅ Markdown files written to: ${path.join(current_dirname, `../summaries/${markdown_type}/`)}`,
);
}
main().catch((error) => {

View File

@@ -0,0 +1,54 @@
import { writeFile, mkdir, rm } from 'node:fs/promises';
import path from 'node:path';
/**
* Export summaries to markdown files in nested directory structure
* @param summaries - Record of slug -> content mappings
* @param type - Type of summaries ('use_cases' or 'distilled')
* @param base_dir - Base directory for summaries (e.g., 'packages/mcp-server/summaries')
* @returns Number of files created
*/
export async function export_summaries_to_markdown(
summaries: Record<string, string>,
type: 'use_cases' | 'distilled',
base_dir: string,
): Promise<number> {
const output_base_dir = path.join(base_dir, type);
const summary_count = Object.keys(summaries).length;
console.log(` 📁 Target: summaries/${type}/`);
console.log(` 📊 Total summaries: ${summary_count}`);
// Clean existing directory for this type
console.log(` 🧹 Cleaning ${type} directory...`);
try {
await rm(output_base_dir, { recursive: true, force: true });
} catch {
// Directory might not exist, that's okay
}
// Create base output directory
await mkdir(output_base_dir, { recursive: true });
let created_count = 0;
// Write each summary to a markdown file
for (const [slug, content] of Object.entries(summaries)) {
const file_path = path.join(output_base_dir, `${slug}.md`);
const file_dir = path.dirname(file_path);
// Create nested directories if needed
await mkdir(file_dir, { recursive: true });
// Write the markdown file
await writeFile(file_path, content, 'utf-8');
created_count++;
if (created_count % 10 === 0) {
console.log(` 📝 Created ${created_count}/${summary_count} files...`);
}
}
console.log(` ✅ Successfully created ${created_count} markdown files`);
return created_count;
}

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env node
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as v from 'valibot';
const current_filename = fileURLToPath(import.meta.url);
const current_dirname = path.dirname(current_filename);
const verification_output_schema = v.object({
generated_at: v.string(),
model: v.string(),
total_sections: v.number(),
verified_sections: v.number(),
accurate_count: v.number(),
not_accurate_count: v.number(),
results: v.array(
v.object({
slug: v.string(),
status: v.union([v.literal('ACCURATE'), v.literal('NOT_ACCURATE')]),
reasoning: v.string(),
}),
),
});
async function main() {
const verification_path = path.join(current_dirname, '../src/distilled-verification.json');
console.log('📂 Reading verification results...\n');
let content: string;
try {
content = await readFile(verification_path, 'utf-8');
} catch {
console.error('❌ Error: Could not find distilled-verification.json');
console.error('Please run `pnpm verify-distilled` first to generate the file.');
process.exit(1);
}
const data = JSON.parse(content);
const validated = v.safeParse(verification_output_schema, data);
if (!validated.success) {
console.error('❌ Error: Invalid verification file format');
console.error(JSON.stringify(validated.issues, null, 2));
process.exit(1);
}
const verification_data = validated.output;
// Filter for NOT_ACCURATE results
const not_accurate = verification_data.results.filter((r) => r.status === 'NOT_ACCURATE');
// Print header
console.log('📊 Verification Results Summary');
console.log('═'.repeat(80));
console.log(`Generated: ${new Date(verification_data.generated_at).toLocaleString()}`);
console.log(`Model: ${verification_data.model}`);
console.log(`Total Sections: ${verification_data.total_sections}`);
console.log(`Verified: ${verification_data.verified_sections}`);
console.log(
`✅ Accurate: ${verification_data.accurate_count} (${((verification_data.accurate_count / verification_data.verified_sections) * 100).toFixed(1)}%)`,
);
console.log(
`❌ Not Accurate: ${verification_data.not_accurate_count} (${((verification_data.not_accurate_count / verification_data.verified_sections) * 100).toFixed(1)}%)`,
);
console.log('═'.repeat(80));
if (not_accurate.length === 0) {
console.log('\n🎉 All sections are accurate! No issues found.');
return;
}
// Print all NOT_ACCURATE entries
console.log(`\n❌ NOT ACCURATE SECTIONS (${not_accurate.length}):\n`);
for (let i = 0; i < not_accurate.length; i++) {
const result = not_accurate[i]!;
console.log(`${i + 1}. ${result.slug}`);
console.log(` Reasoning: ${result.reasoning}`);
console.log('');
}
console.log('═'.repeat(80));
console.log(`\nFound ${not_accurate.length} section(s) that need review or regeneration.`);
}
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,292 @@
#!/usr/bin/env node
import 'dotenv/config';
import { writeFile, mkdir } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { AnthropicProvider } from '../src/lib/anthropic.ts';
import type { AnthropicBatchRequest } from '../src/lib/schemas.ts';
import distilled_data from '../src/distilled.json' with { type: 'json' };
import * as v from 'valibot';
interface VerificationResult {
slug: string;
status: 'ACCURATE' | 'NOT_ACCURATE';
reasoning: string;
}
interface VerificationOutput {
generated_at: string;
model: string;
total_sections: number;
verified_sections: number;
accurate_count: number;
not_accurate_count: number;
results: VerificationResult[];
}
const current_filename = fileURLToPath(import.meta.url);
const current_dirname = path.dirname(current_filename);
const verification_output_schema = v.object({
generated_at: v.string(),
model: v.string(),
total_sections: v.number(),
verified_sections: v.number(),
accurate_count: v.number(),
not_accurate_count: v.number(),
results: v.array(
v.object({
slug: v.string(),
status: v.union([v.literal('ACCURATE'), v.literal('NOT_ACCURATE')]),
reasoning: v.string(),
}),
),
});
const VERIFICATION_PROMPT = `You are tasked with verifying the accuracy of a distilled/condensed version of documentation against the original content.
Your task:
1. Read both the ORIGINAL documentation and the DISTILLED version provided below
2. Determine if the distilled version accurately represents the key information from the original
3. Check for any factual errors, misleading statements, or critical omissions
4. Return your verdict as either "ACCURATE" or "NOT_ACCURATE"
Guidelines for determining accuracy:
- ACCURATE: The distilled version correctly captures the essential information, even if shortened
- ACCURATE: Minor formatting changes or reasonable simplifications are acceptable
- ACCURATE: Examples may be condensed as long as the core concept is preserved
- NOT_ACCURATE: Any factual errors or contradictions with the original
- NOT_ACCURATE: Missing critical information that would mislead developers
- NOT_ACCURATE: Incorrect code examples or API usage
You must respond in exactly this format:
STATUS: [ACCURATE or NOT_ACCURATE]
REASONING: [Brief explanation of your decision in one sentence]
Do not include any other text, formatting, or markdown in your response.`;
function parse_verification_response(text: string): {
status: 'ACCURATE' | 'NOT_ACCURATE';
reasoning: string;
} | null {
// Try to extract STATUS and REASONING using regex
const status_match = text.match(/STATUS:\s*(ACCURATE|NOT_ACCURATE)/i);
const reasoning_match = text.match(/REASONING:\s*(.+?)(?:\n|$)/i);
if (status_match && reasoning_match) {
return {
status: status_match[1]!.toUpperCase() as 'ACCURATE' | 'NOT_ACCURATE',
reasoning: reasoning_match[1]!.trim(),
};
}
// Fallback: try to find just "ACCURATE" or "NOT_ACCURATE" anywhere in the response
const accurate_match = text.match(/\b(NOT_ACCURATE|ACCURATE)\b/i);
if (accurate_match) {
// Extract some context as reasoning
const lines = text.split('\n').filter((line) => line.trim());
const reasoning = lines.slice(0, 3).join(' ').slice(0, 200);
return {
status: accurate_match[1]!.toUpperCase() as 'ACCURATE' | 'NOT_ACCURATE',
reasoning: reasoning || 'Could not extract detailed reasoning',
};
}
return null;
}
async function main() {
console.log('🔍 Starting distilled verification...\n');
const output_path = path.join(current_dirname, '../src/distilled-verification.json');
// Load distilled data
console.log('📂 Loading distilled.json...');
const { summaries, content } = distilled_data;
const sections = Object.keys(summaries);
console.log(`Found ${sections.length} sections to verify\n`);
// Check for API key
const api_key = process.env.ANTHROPIC_API_KEY;
if (!api_key) {
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
console.error('Please set it in packages/mcp-server/.env file or export it:');
console.error('export ANTHROPIC_API_KEY=your_api_key_here');
process.exit(1);
}
// Initialize Anthropic API
console.log('🤖 Initializing Anthropic API...');
const anthropic = new AnthropicProvider('claude-sonnet-4-5-20250929', api_key);
// Prepare batch requests
console.log('📦 Preparing batch requests...');
const batch_requests: AnthropicBatchRequest[] = sections.map((slug, index) => {
const original = content[slug] || '';
const distilled = summaries[slug] || '';
return {
custom_id: `verify-${index}`,
params: {
model: anthropic.get_model_identifier(),
max_tokens: 4096,
messages: [
{
role: 'user',
content:
`${VERIFICATION_PROMPT}\n\n` +
`<original>${original}</original>\n\n` +
`<distilled>${distilled}</distilled>`,
},
],
temperature: 0,
},
};
});
// Create and process batch
console.log('🚀 Creating batch job...');
const batch_response = await anthropic.create_batch(batch_requests);
console.log(`✅ Batch created with ID: ${batch_response.id}`);
// Poll for completion
console.log('⏳ Waiting for batch to complete...');
let batch_status = await anthropic.get_batch_status(batch_response.id);
while (batch_status.processing_status === 'in_progress') {
const { succeeded, processing, errored } = batch_status.request_counts;
console.log(` Progress: ${succeeded} succeeded, ${processing} processing, ${errored} errored`);
await new Promise((resolve) => setTimeout(resolve, 5000));
batch_status = await anthropic.get_batch_status(batch_response.id);
}
console.log('✅ Batch processing completed!');
// Get results
if (!batch_status.results_url) {
throw new Error('Batch completed but no results URL available');
}
console.log('📥 Downloading results...');
const results = await anthropic.get_batch_results(batch_status.results_url);
// Process results
console.log('📊 Processing results...');
const verification_results: VerificationResult[] = [];
for (const result of results) {
const index = parseInt(result.custom_id.split('-')[1] ?? '0');
const slug = sections[index];
if (!slug) {
throw new Error(`Could not find slug for index ${index}`);
}
if (result.result.type !== 'succeeded' || !result.result.message) {
const error_msg = result.result.error?.message || 'Failed or no message';
console.error(` ❌ Failed to verify ${slug}: ${error_msg}`);
verification_results.push({
slug,
status: 'NOT_ACCURATE',
reasoning: `Verification failed: ${error_msg}`,
});
continue;
}
const output_content = result.result.message.content[0]?.text;
if (!output_content) {
console.error(` ❌ No text content in result for ${slug}`);
verification_results.push({
slug,
status: 'NOT_ACCURATE',
reasoning: 'No response from verification',
});
continue;
}
// Parse using regex instead of strict JSON parsing
const parsed = parse_verification_response(output_content);
if (!parsed) {
console.error(` ❌ Failed to parse response for ${slug}`);
console.error(` Raw response: ${output_content.slice(0, 200)}...`);
verification_results.push({
slug,
status: 'NOT_ACCURATE',
reasoning: `Failed to parse verification response: ${output_content.slice(0, 100)}`,
});
continue;
}
verification_results.push({
slug,
status: parsed.status,
reasoning: parsed.reasoning,
});
const emoji = parsed.status === 'ACCURATE' ? '✅' : '❌';
console.log(` ${emoji} ${slug}: ${parsed.status}`);
}
// Calculate statistics
const accurate_count = verification_results.filter((r) => r.status === 'ACCURATE').length;
const not_accurate_count = verification_results.filter((r) => r.status === 'NOT_ACCURATE').length;
// Write output
console.log('\n💾 Writing results to file...');
const output_dir = path.dirname(output_path);
await mkdir(output_dir, { recursive: true });
const output_data: VerificationOutput = {
generated_at: new Date().toISOString(),
model: 'claude-sonnet-4-5-20250929',
total_sections: sections.length,
verified_sections: sections.length,
accurate_count,
not_accurate_count,
results: verification_results,
};
// Validate output before writing
const validated = v.safeParse(verification_output_schema, output_data);
if (!validated.success) {
throw new Error(`Output validation failed: ${JSON.stringify(validated.issues, null, 2)}`);
}
await writeFile(output_path, JSON.stringify(output_data, null, 2), 'utf-8');
// Print summary
console.log('\n📊 Verification Summary:');
console.log(` Total sections: ${sections.length}`);
console.log(` Verified sections: ${sections.length}`);
console.log(
` ✅ Accurate: ${accurate_count} (${((accurate_count / sections.length) * 100).toFixed(1)}%)`,
);
console.log(
` ❌ Not Accurate: ${not_accurate_count} (${((not_accurate_count / sections.length) * 100).toFixed(1)}%)`,
);
if (not_accurate_count > 0) {
console.log('\n⚠ Sections with issues (first 10):');
verification_results
.filter((r) => r.status === 'NOT_ACCURATE')
.slice(0, 10)
.forEach((r) => {
console.log(` - ${r.slug}: ${r.reasoning}`);
});
if (not_accurate_count > 10) {
console.log(` ... and ${not_accurate_count - 10} more`);
}
console.log('\n💡 Run `pnpm show-verification-errors` to see all issues');
}
console.log(`\n✅ Results written to: ${output_path}`);
}
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,875 @@
{
"generated_at": "2025-10-12T22:35:10.647Z",
"model": "claude-sonnet-4-5-20250929",
"total_sections": 173,
"verified_sections": 173,
"accurate_count": 159,
"not_accurate_count": 14,
"results": [
{
"slug": "cli/overview",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about the sv CLI tool, its usage with npx/pnpx, and the behavior of using local vs. downloaded versions, with only the acknowledgements section reasonably omitted."
},
{
"slug": "cli/faq",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including commands for each package manager, troubleshooting context, and references to the same GitHub issues, with only formatting changes and reasonable condensation."
},
{
"slug": "cli/sv-create",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information, options, and examples from the original documentation with appropriate condensation and no factual errors or critical omissions."
},
{
"slug": "cli/sv-add",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including usage, options, and add-ons list, with only minor acceptable simplifications in wording and removal of non-critical elements like links and HTML comments."
},
{
"slug": "cli/sv-check",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information, options, and examples from the original documentation with appropriate condensation and no factual errors or critical omissions."
},
{
"slug": "cli/sv-migrate",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including usage, migration types, and their purposes, with only minor formatting simplifications and no factual errors or critical omissions."
},
{
"slug": "cli/devtools-json",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the plugin's purpose, security note, alternatives, usage, and code example, with only minor formatting simplifications that preserve the core content."
},
{
"slug": "cli/drizzle",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup commands, features, database options, client options, and Docker configuration while maintaining factual accuracy through reasonable condensation."
},
{
"slug": "cli/eslint",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the tool's purpose, installation command, and key components installed, with only minor simplification of wording."
},
{
"slug": "cli/lucia",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the command, what it provides (SvelteKit + Drizzle auth setup following Lucia), and the demo option, with only minor formatting changes and reasonable simplifications."
},
{
"slug": "cli/mdsvex",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the definition, comparison to MDX, installation command, and configuration details, with only minor simplification of wording."
},
{
"slug": "cli/paraglide",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the library description, installation command, included features, and configuration options, with only minor formatting simplifications that preserve the original meaning."
},
{
"slug": "cli/playwright",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information from the original, with only minor formatting changes and reasonable simplification of the bullet points."
},
{
"slug": "cli/prettier",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the tool description, installation command, and what gets added to the project, with only minor rewording that preserves meaning."
},
{
"slug": "cli/storybook",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the installation command, automatic initialization, framework options, and key features like module mocking and link handling."
},
{
"slug": "cli/sveltekit-adapter",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including usage, adapter options with their purposes, and the example command, with only minor formatting simplifications and no factual errors or critical omissions."
},
{
"slug": "cli/tailwind",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup command, installed components, prettier integration, and plugin options, with only minor formatting changes and reasonable simplifications."
},
{
"slug": "cli/vitest",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the framework description, installation command, and the four key outcomes of running the command, with only minor rewording for brevity."
},
{
"slug": "kit/introduction",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential technical information, features, and links from the original, with only the introductory notes and some explanatory prose reasonably condensed."
},
{
"slug": "kit/creating-a-project",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the setup commands, core concepts of pages/routing/rendering, and editor recommendations, with only minor formatting changes and reasonable condensation of explanatory text."
},
{
"slug": "kit/project-types",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information from the original, preserving key concepts, links, and technical details while appropriately condensing explanations and formatting for brevity."
},
{
"slug": "kit/project-structure",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly names the file as \"instrumentation.server.js\" when the original clearly states it is \"tracing.server.js\" in the directory structure, though the description section does mention \"instrumentation.server.js\" creating ambiguity that should have been preserved."
},
{
"slug": "kit/web-standards",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the special fetch behavior, API interfaces, code examples, and usage contexts, with only minor formatting simplifications that preserve the original meaning."
},
{
"slug": "kit/routing",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including routing concepts, file types, code examples, and key behaviors, with appropriate condensation and no factual errors or critical omissions."
},
{
"slug": "kit/load",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including load function types, data flow, URL handling, fetch behavior, cookies, headers, parent data access, errors, redirects, streaming, parallel loading, rerunning logic, authentication implications, and getRequestEvent usage, with appropriate code examples preserved."
},
{
"slug": "kit/form-actions",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key concepts, code examples, and important notes from the original documentation, with appropriate condensation and no factual errors or misleading omissions."
},
{
"slug": "kit/page-options",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the five main page options (prerender, entries, ssr, csr, trailingSlash, config), their usage, code examples, and important warnings, with appropriate condensation but no factual errors or critical omissions."
},
{
"slug": "kit/state-management",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information, preserves critical warnings and code examples, maintains accurate technical details, and only condenses explanatory text while keeping all key concepts intact."
},
{
"slug": "kit/remote-functions",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information including setup, all four function types (query, form, command, prerender), their usage patterns, validation, and key features like batching, single-flight mutations, and error handling, with no factual errors or critical omissions."
},
{
"slug": "kit/building-your-app",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the two-stage build process, the building flag usage with accurate code example, and preview limitations, with only minor formatting simplifications."
},
{
"slug": "kit/adapters",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the purpose of adapters, the list of official adapters, configuration method with code example, and platform-specific context details, with only minor formatting simplifications that don't alter meaning."
},
{
"slug": "kit/adapter-auto",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the adapter list, best practices, configuration limitations, and extension process, with appropriate simplification and no factual errors."
},
{
"slug": "kit/adapter-node",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information including installation, deployment, environment variables, options, graceful shutdown, socket activation, and custom server setup, with appropriate condensation of explanatory text while preserving critical technical details and code examples."
},
{
"slug": "kit/adapter-static",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including installation, configuration, options, and GitHub Pages setup, with appropriate condensation of explanatory text while preserving critical technical details and code examples."
},
{
"slug": "kit/single-page-apps",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including SPA setup, performance warnings, configuration steps, prerendering options, and Apache configuration, with appropriate simplifications and no factual errors."
},
{
"slug": "kit/adapter-cloudflare",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information, configuration options, code examples, and key concepts from the original documentation with appropriate condensation and no factual errors or critical omissions."
},
{
"slug": "kit/adapter-cloudflare-workers",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including deprecation notice, installation, configuration, deployment, runtime APIs, testing, and troubleshooting, with appropriate simplifications and no factual errors."
},
{
"slug": "kit/adapter-netlify",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including installation, configuration options, edge functions, Netlify-specific features, and troubleshooting, with only minor formatting simplifications and no factual errors or critical omissions."
},
{
"slug": "kit/adapter-vercel",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information, configuration options, warnings, and troubleshooting steps from the original, with appropriate condensation and formatting changes that preserve the core technical content."
},
{
"slug": "kit/writing-adapters",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the API structure, required/optional fields, adapt method tasks, and recommendations, with appropriate condensation and reorganization for clarity."
},
{
"slug": "kit/advanced-routing",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key concepts including rest parameters, optional parameters, matchers, sorting rules, encoding, and advanced layouts with correct code examples and essential notes, though condensed for brevity."
},
{
"slug": "kit/hooks",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key hooks, their purposes, parameters, and code examples while appropriately condensing explanatory text and preserving critical technical details."
},
{
"slug": "kit/errors",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including expected/unexpected errors, error handling, responses, type safety, and code examples, with only minor formatting simplifications that preserve the core concepts."
},
{
"slug": "kit/link-options",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all key information, attribute values, code examples, and important warnings from the original documentation, with appropriate condensation but no factual errors or critical omissions."
},
{
"slug": "kit/service-workers",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup, the $service-worker module exports, the complete code example, development considerations, and alternative solutions, with only minor formatting changes and reasonable condensation of explanatory text."
},
{
"slug": "kit/server-only-modules",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the two methods for creating server-only modules, how the protection works, the import chain example, dynamic imports support, and the testing exception, with no factual errors or critical omissions."
},
{
"slug": "kit/snapshots",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the purpose, usage pattern, timing of capture/restore functions, serialization requirements, and the warning about large objects, with only minor formatting simplifications."
},
{
"slug": "kit/shallow-routing",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly states that shallow routing allows you to \"Navigate without triggering `load` functions or replacing page components\" when the original clearly states that navigating through history entries \"re-runs any `load` functions and replaces page components as necessary\" - shallow routing creates history entries WITHOUT navigating, not a way to navigate differently."
},
{
"slug": "kit/observability",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup, traced components, code examples, and important warnings, with only reasonable condensation of explanatory text."
},
{
"slug": "kit/packaging",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including package.json configuration, TypeScript handling, CLI options, and caveats, with appropriate condensation of examples while preserving technical accuracy."
},
{
"slug": "kit/auth",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the definitions, session vs token tradeoffs, integration points, and Lucia implementation details without any factual errors or critical omissions."
},
{
"slug": "kit/performance",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information, performance tips, and technical details from the original, with appropriate condensation and formatting changes that preserve the core concepts and critical guidance."
},
{
"slug": "kit/icons",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the key points about CSS-based icons via Iconify and the warning against per-icon Svelte component libraries that slow down Vite, with appropriate links preserved."
},
{
"slug": "kit/images",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup, usage patterns, configuration details, and best practices, with appropriate condensation of explanatory text while preserving critical technical details and code examples."
},
{
"slug": "kit/accessibility",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including route announcements, focus management, lang attribute configuration, and further reading resources, with appropriate condensation but no factual errors or critical omissions."
},
{
"slug": "kit/seo",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including SSR defaults, performance considerations, URL normalization, manual setup requirements, and complete AMP implementation steps with accurate code examples, though reasonably condensed."
},
{
"slug": "kit/faq",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information, code examples, and key concepts from the original while appropriately condensing explanatory text and maintaining technical accuracy."
},
{
"slug": "kit/integrations",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including vitePreprocess functionality, TypeScript requirements, add-ons list, svelte-preprocess differences, and Vite plugins, with appropriate condensation and no factual errors."
},
{
"slug": "kit/debugging",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup steps, code examples, limitations, and references, with only minor formatting simplifications that preserve the core content."
},
{
"slug": "kit/migrating-to-sveltekit-2",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all key breaking changes, code examples, and migration steps from the original documentation, with appropriate simplifications and no factual errors or critical omissions."
},
{
"slug": "kit/migrating",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key migration steps, API changes, file renames, and code examples from the original documentation without introducing factual errors or omitting critical information."
},
{
"slug": "kit/additional-resources",
"status": "ACCURATE",
"reasoning": "The distilled version preserves all key information including FAQ links, example repositories, community resources, and support channels with appropriate guidance about searching first, using reasonable condensation without factual errors or critical omissions."
},
{
"slug": "kit/glossary",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information, key concepts, default behaviors, configuration options, and important warnings from the original documentation without introducing factual errors or misleading statements."
},
{
"slug": "kit/@sveltejs-kit",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information from the original documentation, including function signatures, type definitions, important warnings, and version availability notes, with appropriate condensation of verbose descriptions while preserving critical technical details."
},
{
"slug": "kit/@sveltejs-kit-hooks",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the behavior of each option, the complete code example, expected output, and type signature with appropriate simplifications in formatting and structure."
},
{
"slug": "kit/@sveltejs-kit-node-polyfills",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the import path, function purpose, the specific APIs (crypto and File), and the TypeScript definition, with only minor formatting changes."
},
{
"slug": "kit/@sveltejs-kit-node",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including function signatures, parameters, and the version availability note, with only minor formatting changes and reasonable additions of clarifying descriptions."
},
{
"slug": "kit/@sveltejs-kit-vite",
"status": "ACCURATE",
"reasoning": "The distilled version preserves all essential information including the import statement, function description, and TypeScript signature, with only minor formatting changes that don't affect accuracy."
},
{
"slug": "kit/$app-environment",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the import statement, the meaning of each exported constant, their types, and important caveats, with only minor wording simplifications that preserve accuracy."
},
{
"slug": "kit/$app-forms",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including function purposes, parameters, return types, code examples, and the detailed behavior of the enhance function's default and custom callback options."
},
{
"slug": "kit/$app-navigation",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key information, function signatures, and important behavioral details from the original documentation, with appropriate condensation that preserves essential meaning."
},
{
"slug": "kit/$app-paths",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including function signatures, usage examples, and deprecation notices, with only minor formatting simplifications that preserve the core content."
},
{
"slug": "kit/$app-server",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key information including function signatures, version availability, descriptions, and code examples, with only minor formatting simplifications that preserve the essential content."
},
{
"slug": "kit/$app-state",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the three state objects, their properties, usage examples, the critical runes vs legacy reactivity distinction, and server/browser behavior differences, with only minor formatting simplifications."
},
{
"slug": "kit/$app-stores",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including deprecation notices, functionality of each store, server/browser subscription constraints, and type signatures, with only minor formatting simplifications."
},
{
"slug": "kit/$app-types",
"status": "NOT_ACCURATE",
"reasoning": "The LayoutParams type definition incorrectly shows \"RouteParams\" instead of \"LayoutParams\" in the distilled version, which is a factual error copied from the original."
},
{
"slug": "kit/$env-dynamic-private",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the key rules about prefix filtering, client-side restriction, usage example, and the dev/prod behavior note."
},
{
"slug": "kit/$env-dynamic-public",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the public prefix requirement, client-side safety, network payload concern, recommendation to prefer static version, and preserves the accurate code example."
},
{
"slug": "kit/$env-static-private",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the static injection behavior, prefix filtering rules, server-only nature, and best practices, with appropriate simplification and reformatting."
},
{
"slug": "kit/$env-static-public",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the PUBLIC_ prefix requirement, configurability, client-side safety, build-time replacement, and preserves the code example accurately."
},
{
"slug": "kit/$lib",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about the $lib import alias, its default location, configurability, and includes the same code examples demonstrating its usage."
},
{
"slug": "kit/$service-worker",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the module availability, each export's type and purpose, development behavior, and configuration references without introducing factual errors or omitting critical details."
},
{
"slug": "kit/configuration",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential configuration options, defaults, code examples, and important notes from the original documentation, with appropriate condensation and no factual errors or critical omissions."
},
{
"slug": "kit/cli",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including Vite CLI commands, svelte-kit sync functionality, and automatic execution, with only minor simplifications that preserve meaning."
},
{
"slug": "kit/types",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including generated types, helper types, setup requirements, $lib aliases, and app.d.ts interfaces, with appropriate code examples and version-specific details preserved."
},
{
"slug": "mcp/overview",
"status": "ACCURATE",
"reasoning": "The distilled version preserves all critical information including setup options, the complete usage prompt, and tool descriptions, with only minor formatting simplifications that don't alter meaning."
},
{
"slug": "mcp/local-setup",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including installation commands, configuration examples for each client, and maintains factual accuracy with appropriate condensation of instructional text."
},
{
"slug": "mcp/remote-setup",
"status": "ACCURATE",
"reasoning": "The distilled version correctly preserves all essential setup instructions, commands, and configuration details for each client while appropriately condensing explanatory text and formatting."
},
{
"slug": "mcp/tools",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all four tools and their essential functions, with only minor formatting changes and reasonable condensation of descriptions."
},
{
"slug": "mcp/resources",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about the doc-section resource including URI format, return type, and use case, with only minor formatting changes and reasonable simplification."
},
{
"slug": "mcp/prompts",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version omits critical information about the extensive list of available documentation paths with their specific use cases, which is essential for the LLM to know when to invoke the get_documentation tool with the correct path parameter."
},
{
"slug": "svelte/overview",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about Svelte being a compiler-based framework, preserves the complete code example, and accurately notes the key syntax detail about event handlers and SvelteKit usage."
},
{
"slug": "svelte/getting-started",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including setup commands, alternatives, tooling options, and help resources without introducing factual errors or omitting critical details."
},
{
"slug": "svelte/svelte-files",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including component structure, script behaviors, module-level logic, scoping rules, and includes the key code examples, with only minor formatting simplifications and removal of non-critical notes."
},
{
"slug": "svelte/svelte-js-files",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the file types, their behavior, use cases, and the critical limitation about exporting reassigned state."
},
{
"slug": "svelte/what-are-runes",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information about runes including their purpose, syntax, and three key characteristics, with only the etymology note and legacy information reasonably omitted."
},
{
"slug": "svelte/$state",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including key concepts, code examples, gotchas, and the two options for sharing state across modules, with appropriate simplifications and no factual errors."
},
{
"slug": "svelte/$derived",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key concepts including $derived, $derived.by, dependencies, overriding values, reactivity behavior, destructuring, and update propagation with correct code examples and essential details preserved."
},
{
"slug": "svelte/$effect",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key concepts, correctly represents the code examples, maintains critical warnings about when not to use effects, and preserves the essential technical details about lifecycle, dependencies, and the various effect runes."
},
{
"slug": "svelte/$props",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including props usage, fallback values, renaming, rest props, updating behavior with warnings about mutation, type safety examples, and $props.id() functionality, with appropriate condensation of examples while preserving core concepts."
},
{
"slug": "svelte/$bindable",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the bidirectional data flow concept, $bindable syntax, usage examples, fallback values, and the warning about sparing use, with only minor formatting changes."
},
{
"slug": "svelte/$inspect",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including development-only behavior, deep reactivity tracking, the `.with()` method signature, `$inspect.trace()` requirements, and preserves critical details like the first-statement requirement and optional label parameter."
},
{
"slug": "svelte/$host",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about the $host rune, preserves the code examples accurately, and adds helpful key points without introducing any factual errors or contradictions."
},
{
"slug": "svelte/basic-markup",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including tags, attributes, props, events, delegation, text expressions, and comments with accurate technical details and no misleading omissions."
},
{
"slug": "svelte/if",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, examples, and the note about blocks wrapping elements or text, with only minor formatting improvements."
},
{
"slug": "svelte/each",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, keyed blocks, destructuring, itemless iteration, and else clauses with appropriate examples and explanations."
},
{
"slug": "svelte/key",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, core behavior (destroy/recreate on change), and both key use cases (component reinstantiation and transition replay) with accurate code examples."
},
{
"slug": "svelte/await",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly states that omitting the catch block is a \"shorthand pattern\" when the original shows this requires omitting the initial pending block as well, and it fails to mention that the catch block can be omitted while keeping the pending block."
},
{
"slug": "svelte/snippet",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, scope rules, component passing methods, TypeScript typing, exporting capabilities, and the key restrictions, with appropriate condensation of examples while preserving core concepts."
},
{
"slug": "svelte/@render",
"status": "ACCURATE",
"reasoning": "The distilled version preserves all key information including syntax, code examples, optional chaining usage, and fallback patterns with only minor formatting changes and reasonable simplifications."
},
{
"slug": "svelte/@html",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly shows `article :global` (with a space) instead of `article :global(*)` or the nested syntax shown in the original, which would target different elements than intended."
},
{
"slug": "svelte/@attach",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including basic usage, attachment factories, inline attachments, component passing, reactivity control, and utility functions, with appropriate code examples and key concepts preserved."
},
{
"slug": "svelte/@const",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about {@const} tag usage, its purpose, code example, and placement restrictions, with only minor formatting differences."
},
{
"slug": "svelte/@debug",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the core functionality, syntax rules, valid/invalid examples, and the no-arguments behavior, with only minor formatting improvements."
},
{
"slug": "svelte/bind",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, binding types, version-specific features, and important notes/warnings, with appropriate condensation of examples while preserving core concepts."
},
{
"slug": "svelte/use",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the note about attachments, action syntax, argument usage, the key behavior that actions don't re-run on argument changes, and typing details, with only minor formatting simplifications."
},
{
"slug": "svelte/transition",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including transition behavior, local vs global distinctions, parameters, custom functions with css/tick details, and transition events, with appropriate condensation but no factual errors or critical omissions."
},
{
"slug": "svelte/in-and-out",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the key concepts of non-bidirectional behavior, simultaneous play of in/out transitions, and restart behavior when aborted, with the same code example preserved."
},
{
"slug": "svelte/animate",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including animation triggers, usage patterns, custom function signatures, and the distinction between css and tick methods, with appropriate simplifications and no factual errors."
},
{
"slug": "svelte/style",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including basic usage, expressions, shorthand form, multiple styles, important modifier, and precedence rules with accurate code examples."
},
{
"slug": "svelte/class",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the two ways to set classes, version-specific features, code examples, and the recommendation to prefer the class attribute over the class: directive."
},
{
"slug": "svelte/await-expressions",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the experimental flag configuration, synchronized updates behavior, concurrency rules, loading state handling, error handling, SSR support, and caveats, with appropriate code examples preserved."
},
{
"slug": "svelte/scoped-styles",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including scoped styles, specificity increases, :where() usage, and scoped keyframes with accurate technical details and preserved code examples."
},
{
"slug": "svelte/global-styles",
"status": "ACCURATE",
"reasoning": "The distilled version preserves all essential information including syntax, examples, and key concepts about global styling and keyframes, with only minor formatting changes and removal of a non-critical note about alternative selector syntax."
},
{
"slug": "svelte/custom-properties",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, desugaring behavior, SVG handling, var() usage with fallbacks, inheritance, and the CSS selector gotcha, with only minor formatting changes."
},
{
"slug": "svelte/nested-style-elements",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all key information including the one top-level style tag limit, nested style tag behavior, lack of scoping/processing for nested tags, and preserves the illustrative code example."
},
{
"slug": "svelte/svelte-boundary",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly states that boundaries only catch errors during \"top-level $effect\" when the original says \"running effects\" more broadly, and it omits the critical note about the playground's built-in boundary behavior for the pending snippet."
},
{
"slug": "svelte/svelte-window",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, rules, event listener usage, bindable properties with their readonly/writable distinctions, and the important scrolling behavior note."
},
{
"slug": "svelte/svelte-document",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, usage constraints, event handling, actions support, and the four readonly bindable properties without any factual errors or critical omissions."
},
{
"slug": "svelte/svelte-body",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including syntax, usage, supported features (events and actions), and the critical constraint about top-level placement."
},
{
"slug": "svelte/svelte-head",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including functionality, SSR behavior, placement restrictions, and preserves the code example accurately."
},
{
"slug": "svelte/svelte-element",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including usage, limitations, void element behavior, nullish handling, namespace requirements, and valid tag restrictions without any factual errors or critical omissions."
},
{
"slug": "svelte/svelte-options",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version omits critical information about deprecated Svelte 4 options (immutable and accessors) and removes important reference links to the compiler section, Legacy APIs section, and custom elements component options documentation."
},
{
"slug": "svelte/stores",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information including the $ prefix syntax, when to use stores vs runes in Svelte 5, all API methods with correct examples, and the store contract requirements without any factual errors or critical omissions."
},
{
"slug": "svelte/context",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly shows `counter.count = 0` in the \"Correct\" example when the original uses `+++counter.count = 0+++` (which appears to be markup indicating emphasis, but the actual code should be `counter.count = 0`), and more critically, it omits the important context about when Parent.svelte renders Child.svelte as part of a children snippet, which is described as \"particularly useful\" in the original."
},
{
"slug": "svelte/lifecycle-hooks",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including lifecycle concepts, API usage, server-side behavior notes, cleanup patterns, the deprecation of beforeUpdate/afterUpdate, and the chat autoscroll example with accurate code."
},
{
"slug": "svelte/imperative-component-api",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including API signatures, key behavioral differences from Svelte 4, return values, and critical notes about effects and flushSync, with only minor formatting improvements."
},
{
"slug": "svelte/testing",
"status": "ACCURATE",
"reasoning": "The distilled version preserves all critical information, code examples, and key concepts from the original while appropriately condensing explanatory text and maintaining technical accuracy."
},
{
"slug": "svelte/typescript",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including TypeScript setup, limitations, configuration requirements, typing patterns, and code examples, with appropriate condensation but no factual errors or critical omissions."
},
{
"slug": "svelte/custom-elements",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all essential information including lifecycle behavior, component options, usage patterns, and caveats, with appropriate condensation and preserved code examples."
},
{
"slug": "svelte/v4-migration-guide",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key migration information, requirements, code examples, and breaking changes from the original documentation, with appropriate condensation and formatting that preserves the essential technical details."
},
{
"slug": "svelte/v5-migration-guide",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key migration information, breaking changes, and code examples from the original documentation, with appropriate condensation while preserving critical technical details and warnings."
},
{
"slug": "svelte/faq",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information from the original, with appropriate condensation of explanations while preserving key facts, links, code examples, and critical details like the Svelte Native limitation in Svelte 5."
},
{
"slug": "svelte/svelte",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information, properly marks deprecated features, preserves critical code examples and type signatures, and accurately represents the migration guidance from Svelte 4 to Svelte 5."
},
{
"slug": "svelte/svelte-action",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly states \"Use `use:` directive to apply them\" which is not mentioned in the original documentation and could be misleading since the original explicitly states actions have been superseded by attachments."
},
{
"slug": "svelte/svelte-animate",
"status": "ACCURATE",
"reasoning": "The distilled version correctly preserves all key information including the flip function signature, its purpose, the FLIP acronym explanation, and complete interface definitions for AnimationConfig and FlipParams."
},
{
"slug": "svelte/svelte-attachments",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version omits the critical \"Available since 5.29\" version information for createAttachmentKey, which is essential for developers to know compatibility requirements."
},
{
"slug": "svelte/svelte-compiler",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including function signatures, key options with defaults, type definitions, and important notes about deprecations and version changes, with appropriate simplifications that preserve meaning."
},
{
"slug": "svelte/svelte-easing",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the import path, function signature, all available easing functions with their variants, and adds helpful context about the parameter/return value ranges and easing types without introducing any factual errors."
},
{
"slug": "svelte/svelte-events",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential functionality, key benefit, and API usage, with the type signatures reasonably simplified into a single generic form that encompasses all the specific overloads shown in the original."
},
{
"slug": "svelte/svelte-legacy",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all key functions, their purposes, and type signatures while appropriately condensing descriptions and maintaining the deprecated/temporary nature of the module."
},
{
"slug": "svelte/svelte-motion",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including API signatures, key properties, methods, availability dates, deprecation notices, and code examples, with appropriate simplifications that preserve the core concepts."
},
{
"slug": "svelte/svelte-reactivity-window",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the reactive nature, usage examples, all exports, server-side behavior, and critical implementation details like browser differences and requestAnimationFrame updates."
},
{
"slug": "svelte/svelte-reactivity",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key information, including API signatures, critical warnings (SSR/hydration, deep reactivity), and complete code examples, with only minor formatting simplifications."
},
{
"slug": "svelte/svelte-server",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version incorrectly simplifies the type signature by removing the conditional types that make props required when the component has required props (the `{} extends Props` conditional logic and the two different function overloads)."
},
{
"slug": "svelte/svelte-store",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential functions, their signatures, and key type definitions with appropriate simplifications to descriptions while maintaining technical accuracy."
},
{
"slug": "svelte/svelte-transition",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including function signatures, parameters, behavior descriptions, and type definitions, with only minor formatting simplifications and reasonable condensation of explanatory text."
},
{
"slug": "svelte/compiler-errors",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key error messages, their meanings, and critical code examples while appropriately condensing the content without introducing factual errors or misleading information."
},
{
"slug": "svelte/compiler-warnings",
"status": "ACCURATE",
"reasoning": "The distilled version accurately captures all key warnings with their essential information, code examples, and solutions, using appropriate condensation while preserving critical details for developers."
},
{
"slug": "svelte/runtime-errors",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version contains a syntax error in the effect_update_depth_exceeded section with an extra closing brace (`});` appears twice), which would mislead developers trying to understand the code example."
},
{
"slug": "svelte/runtime-warnings",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all key warnings with their essential explanations and code examples, using reasonable condensation while preserving critical technical details and fixes."
},
{
"slug": "svelte/legacy-overview",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the key information about Svelte 5 changes, the two modes, and the reference to v4 documentation, with only minor simplifications that preserve the essential meaning."
},
{
"slug": "svelte/legacy-let",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the auto-reactive nature of top-level variables, the array methods gotcha, and preserves the code examples accurately, with only minor formatting changes for condensation."
},
{
"slug": "svelte/legacy-reactive-assignments",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including basic usage, topological ordering, dependencies, gotchas, and browser-only code, with appropriate simplifications and no factual errors."
},
{
"slug": "svelte/legacy-export-let",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including prop declaration syntax, default value behavior, the critical difference about undefined values not reverting, component exports, and prop renaming, with appropriate code examples preserved."
},
{
"slug": "svelte/legacy-$$props-and-$$restProps",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the distinction between legacy and runes mode, the definitions of $$props and $$restProps, the complete code example, and the performance warning."
},
{
"slug": "svelte/legacy-on",
"status": "NOT_ACCURATE",
"reasoning": "The distilled version omits the legacy createEventDispatcher implementation details and example usage that are still part of the current documentation, which could mislead developers who need to work with existing code using that pattern."
},
{
"slug": "svelte/legacy-slots",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including basic slots, named slots, fallback content, and passing data to slots, with appropriate code examples and no factual errors or critical omissions."
},
{
"slug": "svelte/legacy-$$slots",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential information about $$slots in legacy mode, preserves the code examples intact, and appropriately notes the runes mode alternative, with only minor formatting changes."
},
{
"slug": "svelte/legacy-svelte-fragment",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures the essential functionality, preserves the complete code examples, and accurately conveys the Svelte 5+ obsolescence note, with only minor acceptable formatting changes."
},
{
"slug": "svelte/legacy-svelte-component",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all key information including the legacy mode behavior, runes mode alternative, and the falsy condition, with appropriate reorganization for clarity."
},
{
"slug": "svelte/legacy-svelte-self",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including the recursive functionality, the requirement to prevent infinite loops, the code example, and the obsolescence note with the self-import alternative."
},
{
"slug": "svelte/legacy-component-api",
"status": "ACCURATE",
"reasoning": "The distilled version correctly captures all essential information including API methods, options tables, key behaviors (async vs sync updates), and Svelte 5 migration notes, with appropriate condensation of explanatory text while preserving critical technical details."
}
]
}

File diff suppressed because one or more lines are too long

View File

@@ -17,24 +17,8 @@ export const summary_data_schema = v.object({
model: v.string(),
total_sections: v.number(),
successful_summaries: v.number(),
failed_summaries: v.number(),
summaries: v.record(v.string(), v.string()),
errors: v.optional(
v.array(
v.object({
section: v.string(),
error: v.string(),
}),
),
),
download_errors: v.optional(
v.array(
v.object({
section: v.string(),
error: v.string(),
}),
),
),
content: v.record(v.string(), v.string()),
});
export const anthropic_batch_request_schema = v.object({

View File

@@ -1,6 +1,11 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { get_sections, fetch_with_timeout, format_sections_list } from '../../utils.js';
import {
get_sections,
fetch_with_timeout,
format_sections_list,
get_distilled_content,
} from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
export function get_documentation(server: SvelteMcp) {
@@ -8,7 +13,7 @@ export function get_documentation(server: SvelteMcp) {
{
name: 'get-documentation',
description:
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
'Retrieves documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
schema: v.object({
section: v.pipe(
v.union([v.string(), v.array(v.string())]),
@@ -16,9 +21,18 @@ export function get_documentation(server: SvelteMcp) {
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "cli/overview"). Supports single string and array of strings',
),
),
use_distilled: v.optional(
v.pipe(
v.boolean(),
v.description(
'If true (default), returns condensed distilled versions of the documentation to optimize context size. Set to false ONLY if the user asks to fetch full documentation.',
),
),
true,
),
}),
},
async ({ section }) => {
async ({ section, use_distilled = true }) => {
let sections: string[];
if (Array.isArray(section)) {
@@ -46,6 +60,19 @@ export function get_documentation(server: SvelteMcp) {
const available_sections = await get_sections();
// Sections that are never distilled, since they contain reference information where things might go missing in distillation
const ALWAYS_FULL_SECTIONS = [
'kit/@sveltejs-kit',
'svelte/v5-migration-guide',
'kit/remote-functions',
'kit/configuration',
'mcp/prompts',
'svelte/compiler-warnings',
'svelte/svelte-compiler',
'svelte/compiler-errors',
'svelte/svelte',
];
const settled_results = await Promise.allSettled(
sections.map(async (requested_section) => {
const matched_section = available_sections.find(
@@ -56,6 +83,24 @@ export function get_documentation(server: SvelteMcp) {
);
if (matched_section) {
// Force full documentation for specific sections
const should_use_distilled =
use_distilled && !ALWAYS_FULL_SECTIONS.includes(matched_section.slug);
console.log(
`Fetching documentation for section "${matched_section.title}" (${should_use_distilled ? 'distilled' : 'full'})`,
);
if (should_use_distilled) {
const distilled = get_distilled_content(matched_section.slug);
if (distilled) {
return {
success: true,
content: `## ${matched_section.title}\n\n${distilled}`,
};
}
// If no distilled content, fall through to fetch full content
}
try {
const response = await fetch_with_timeout(matched_section.url);
if (response.ok) {

View File

@@ -3,3 +3,80 @@ export const SECTIONS_LIST_INTRO =
export const SECTIONS_LIST_OUTRO =
"Carefully analyze the use_cases field for each section to identify which documentation is relevant for the user's specific query. The use_cases contain keywords for project types, features, components, and development stages. After identifying relevant sections, use the get-documentation tool with ALL relevant section titles or paths at once (can pass multiple sections as an array).";
export const USE_CASES_PROMPT = `You are tasked with analyzing Svelte 5 and SvelteKit documentation pages to identify when they would be useful.
Your task:
1. Read the documentation page content provided
2. Identify the main use cases, scenarios, or queries where this documentation would be relevant
3. Create a VERY SHORT, comma-separated list of use cases (maximum 200 characters total)
4. Think about what a developer might be trying to build or accomplish when they need this documentation
Guidelines:
- Focus on WHEN this documentation would be needed, not WHAT it contains
- Consider specific project types (e.g., "e-commerce site", "blog", "dashboard", "social media app")
- Consider specific features (e.g., "authentication", "forms", "data fetching", "animations")
- Consider specific components (e.g., "slider", "modal", "dropdown", "card")
- Consider development stages (e.g., "project setup", "deployment", "testing", "migration")
- Use "always" for fundamental concepts that apply to virtually all Svelte projects
- Be concise but specific
- Use lowercase
- Separate multiple use cases with commas
Examples of good use_cases:
- "always, any svelte project, core reactivity"
- "authentication, login systems, user management"
- "e-commerce, product listings, shopping carts"
- "forms, user input, data submission"
- "deployment, production builds, hosting setup"
- "animation, transitions, interactive ui"
- "routing, navigation, multi-page apps"
- "blog, content sites, markdown rendering"
Requirements:
- Maximum 200 characters (including spaces and commas)
- Lowercase only
- Comma-separated list of use cases
- Focus on WHEN/WHY someone would need this, not what it is
- Be specific about project types, features, or components when applicable
- Use "always" sparingly, only for truly universal concepts
- Do not include quotes or special formatting in your response
- Respond with ONLY the use cases text, no additional text
Here is the documentation page content to analyze:
`;
export const DISTILLED_PROMPT = `You are an expert in web development, specifically Svelte 5 and SvelteKit. Your task is to condense and distill a section of the Svelte documentation that will be provided inside the "<documentation>" tag below, into a concise format while preserving the most important information.
Shorten the text information AS MUCH AS POSSIBLE while covering key concepts.
Focus on:
1. Code examples with short explanations of how they work
2. Key concepts and APIs with their usage patterns
3. Important gotchas and best practices
4. Patterns that developers commonly use
Remove:
1. Redundant explanations
2. Verbose content that can be simplified
3. Marketing language
4. Legacy or deprecated content
5. Anything else that is not strictly necessary
Keep your output in markdown format. Preserve code blocks with their language annotations.
Maintain headings but feel free to combine or restructure sections to improve clarity.
Make sure all code examples use Svelte 5 runes syntax ($state, $derived, $effect, etc.)
Keep the following Svelte 5 syntax rules in mind whend distilling the documentation:
* There is no colon (:) in event modifiers. You MUST use "onclick" instead of "on:click".
* Runes do not need to be imported, they are globals.
* $state() runes are always declared using let, never with const.
* When passing a function to $derived, you must always use $derived.by(() => ...).
* Error boundaries can only catch errors during component rendering and at the top level of an $effect inside the error boundary.
* Error boundaries do not catch errors in onclick or other event handlers.
IMPORTANT: All code examples MUST come from the documentation verbatim, do NOT create new code examples. Do NOT modify existing code examples.
IMPORTANT: Because of changes in Svelte 5 syntax, do not include content from your existing knowledge, you may only use knowledge from the <documentation> tag below.
Here is the documentation you must condense:
`;

View File

@@ -1,6 +1,7 @@
import * as v from 'valibot';
import { documentation_sections_schema } from '../lib/schemas.js';
import summary_data from '../use_cases.json' with { type: 'json' };
import distilled_data from '../distilled.json' with { type: 'json' };
export async function fetch_with_timeout(
url: string,
@@ -18,6 +19,16 @@ export async function fetch_with_timeout(
}
const summaries = (summary_data.summaries || {}) as Record<string, string>;
const distilled_summaries = (distilled_data.summaries || {}) as Record<string, string>;
const full_data = (distilled_data.content || {}) as Record<string, string>;
export function get_distilled_content(slug: string): string | null {
return distilled_summaries[slug] ?? null;
}
export function get_cached_content(slug: string): string | null {
return full_data[slug] ?? null;
}
export async function get_sections() {
const sections = await fetch_with_timeout(

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
# devtools-json Add-on
Installs [`vite-plugin-devtools-json`](https://github.com/ChromeDevTools/vite-plugin-devtools-json/) - a Vite plugin that generates a Chromium DevTools settings file at `/.well-known/appspecific/com.chrome.devtools.json`. Enables [workspaces feature](https://developer.chrome.com/docs/devtools/workspaces) to edit source files directly in Chromium browsers.
> [!NOTE]
> Enables feature for all dev server users with Chromium browsers. Allows browser to read/write all files in directory. Chrome's AI Assistance may send data to Google.
## Alternatives
**Disable in browser:** Visit `chrome://flags` and disable "DevTools Project Settings" and "DevTools Automatic Workspace Folders".
**Handle request manually:** Create `.well-known/appspecific/com.chrome.devtools.json` file or add to `handle` hook:
```js
/// file: src/hooks.server.js
import { dev } from '$app/environment';
export function handle({ event, resolve }) {
if (dev && event.url.pathname === '/.well-known/appspecific/com.chrome.devtools.json') {
return new Response(undefined, { status: 404 });
}
return resolve(event);
}
```
## Usage
```sh
npx sv add devtools-json
```
Adds `vite-plugin-devtools-json` to Vite config.

View File

@@ -0,0 +1,46 @@
# Drizzle ORM
TypeScript ORM with relational and SQL-like query APIs, serverless-ready.
## Setup
```sh
npx sv add drizzle
```
**Includes:**
- Database access in SvelteKit server files
- `.env` for credentials
- Lucia auth compatibility
- Optional Docker config for local database
## Options
### database
Choose database variant:
- `postgresql` — popular open source
- `mysql` — popular open source
- `sqlite` — file-based, no server needed
```sh
npx sv add drizzle=database:postgresql
```
### client
SQL client (depends on database):
- **postgresql**: `postgres.js`, `neon`
- **mysql**: `mysql2`, `planetscale`
- **sqlite**: `better-sqlite3`, `libsql`, `turso`
```sh
npx sv add drizzle=database:postgresql+client:postgres.js
```
*Note: Can swap for [other Drizzle-compatible drivers](https://orm.drizzle.team/docs/connect-overview#next-steps) after setup*
### docker
Add Docker Compose config (postgresql/mysql only):
```sh
npx sv add drizzle=database:postgresql+client:postgres.js+docker:yes
```

View File

@@ -0,0 +1,15 @@
# ESLint
Lints and fixes code problems.
## Setup
```sh
npx sv add eslint
```
Installs:
- `eslint-plugin-svelte`
- `eslint.config.js`
- Updated `.vscode/settings.json`
- Auto-configured for TypeScript and Prettier if present

View File

@@ -0,0 +1,20 @@
# sv CLI
## Running sv CLI
```bash
npm: npx sv create
pnpm: pnpx sv create / pnpm dlx sv create
bun: bunx sv create
deno: deno run npm:sv create
yarn: yarn dlx sv create
```
## Troubleshooting
If `npx sv` doesn't work, package managers may prioritize local tools over registry packages. Common issues:
- Command does nothing
- Name collision with `runit`
- Windows PowerShell conflict with `Set-Variable`
See: [GitHub issues #472](https://github.com/sveltejs/cli/issues/472), [#259](https://github.com/sveltejs/cli/issues/259), [#317](https://github.com/sveltejs/cli/issues/317)

View File

@@ -0,0 +1,18 @@
# Lucia Auth
Auth setup following [Lucia auth guide](https://lucia-auth.com/).
## Usage
```sh
npx sv add lucia
```
Adds auth setup for SvelteKit + Drizzle following Lucia best practices.
## Options
**demo** - Include demo registration/login pages:
```sh
npx sv add lucia=demo:yes
```

View File

@@ -0,0 +1,11 @@
# mdsvex
[mdsvex](https://mdsvex.pngwn.io) is a markdown preprocessor for Svelte - allows using Svelte components in markdown and vice versa.
## Installation
```sh
npx sv add mdsvex
```
Installs and configures mdsvex in `svelte.config.js`.

View File

@@ -0,0 +1,13 @@
# CLI (`sv`)
Toolkit for creating and maintaining Svelte applications.
## Usage
Run with `npx` (or `pnpx` for pnpm):
```sh
npx sv <command> <args>
```
Uses local installation if available, otherwise downloads latest version without installing.

View File

@@ -0,0 +1,30 @@
# Paraglide i18n
Compiler-based i18n library with tree-shakable messages, small bundles, type-safety.
## Installation
```sh
npx sv add paraglide
```
## Includes
- Inlang project settings
- Paraglide Vite plugin
- SvelteKit `reroute` and `handle` hooks
- `text-direction` and `lang` attributes in `app.html`
- Updated `.gitignore`
- Optional demo page
## Options
**languageTags** - IETF BCP 47 language tags:
```sh
npx sv add paraglide="languageTags:en,es"
```
**demo** - Generate demo page:
```sh
npx sv add paraglide="demo:yes"
```

View File

@@ -0,0 +1,15 @@
# Playwright
Browser testing with [Playwright](https://playwright.dev).
## Usage
```sh
npx sv add playwright
```
Adds:
- Test scripts to `package.json`
- Playwright config file
- Updated `.gitignore`
- Demo test

View File

@@ -0,0 +1,15 @@
# Prettier
Opinionated code formatter for Svelte projects.
## Usage
```sh
npx sv add prettier
```
## Adds
- `package.json` scripts
- `.prettierignore` and `.prettierrc` config files
- ESLint config updates (if ESLint installed)

View File

@@ -0,0 +1,15 @@
# Storybook
Component workshop for Svelte/SvelteKit.
## Installation
```sh
npx sv add storybook
```
## Features
- Runs `npx storybook init` automatically
- Configures either [Storybook for SvelteKit](https://storybook.js.org/docs/get-started/frameworks/sveltekit) or [Storybook for Svelte & Vite](https://storybook.js.org/docs/get-started/frameworks/svelte-vite)
- Includes SvelteKit module mocking and automatic link handling

View File

@@ -0,0 +1,37 @@
# sv add
Adds functionality to existing Svelte projects.
## Usage
```sh
npx sv add
```
```sh
npx sv add [add-ons]
```
Select multiple space-separated add-ons or use interactive prompt.
## Options
- `-C`, `--cwd` — path to project root
- `--no-git-check` — skip dirty files prompt
- `--install` — install dependencies with specified package manager
- `--no-install` — skip dependency installation
## Official add-ons
- `devtools-json`
- `drizzle`
- `eslint`
- `lucia`
- `mdsvex`
- `paraglide`
- `playwright`
- `prettier`
- `storybook`
- `sveltekit-adapter`
- `tailwindcss`
- `vitest`

View File

@@ -0,0 +1,109 @@
# sv check
Finds errors and warnings in your project: unused CSS, accessibility hints, JS/TS compiler errors.
Requires Node 16+.
## Installation
```sh
npm i -D svelte-check
```
## Usage
```sh
npx sv check
```
## Options
### `--workspace <path>`
Path to workspace. Checks all subdirectories except `node_modules` and ignored paths.
### `--output <format>`
Display format: `human`, `human-verbose`, `machine`, `machine-verbose`
### `--watch`
Watch mode for changes.
### `--preserveWatchOutput`
Don't clear screen in watch mode.
### `--tsconfig <path>`
Path to `tsconfig`/`jsconfig`. Only files matched by config's `files`/`include`/`exclude` are checked. Reports errors from TS/JS files. If not provided, searches upward from project directory.
### `--no-tsconfig`
Only check Svelte files, ignore `.js`/`.ts` files.
### `--ignore <paths>`
Comma-separated quoted paths relative to workspace root:
```sh
npx sv check --ignore "dist,build"
```
Only effective with `--no-tsconfig` for diagnostics. With `--tsconfig`, only affects watched files.
### `--fail-on-warnings`
Exit with error code on warnings.
### `--compiler-warnings <warnings>`
Comma-separated `code:behaviour` pairs (`ignore` or `error`):
```sh
npx sv check --compiler-warnings "css_unused_selector:ignore,a11y_missing_attribute:error"
```
### `--diagnostic-sources <sources>`
Comma-separated sources (default: all active):
- `js` (includes TypeScript)
- `svelte`
- `css`
```sh
npx sv check --diagnostic-sources "js,svelte"
```
### `--threshold <level>`
Filter diagnostics:
- `warning` (default) — errors and warnings
- `error` — only errors
## Machine-readable output
`--output machine` or `machine-verbose` formats output for CI/automation.
Each row: columns separated by single space. First column: timestamp (ms). Second column: row type.
**START row:** workspace folder (quoted)
```
1590680325583 START "/home/user/language-tools/packages/language-server/test/plugins/typescript/testfiles"
```
**machine format:** filename, line, column, message (quoted)
```
1590680326283 ERROR "codeactions.svelte" 1:16 "Cannot find module 'blubb' or its corresponding type declarations."
1590680326778 WARNING "imported-file.svelte" 0:37 "Component has unused export property 'prop'. If it is for external reference only, please consider using `export const prop`"
```
**machine-verbose format:** ndjson with timestamp prefix
```
1590680326283 {"type":"ERROR","fn":"codeaction.svelte","start":{"line":1,"character":16},"end":{"line":1,"character":23},"message":"Cannot find module 'blubb' or its corresponding type declarations.","code":2307,"source":"js"}
1590680326778 {"type":"WARNING","filename":"imported-file.svelte","start":{"line":0,"character":37},"end":{"line":0,"character":51},"message":"Component has unused export property 'prop'. If it is for external reference only, please consider using `export const prop`","code":"unused-export-let","source":"svelte"}
```
**COMPLETED row:** summary
```
1590680326807 COMPLETED 20 FILES 21 ERRORS 1 WARNINGS 3 FILES_WITH_PROBLEMS
```
**FAILURE row:** runtime errors
```
1590680328921 FAILURE "Connection closed"
```
## FAQ
**Why no option to check only specific files?**
`svelte-check` needs the whole project for valid checks. Partial checks miss errors in unchanged files (e.g., renamed prop not updated at usage sites).

View File

@@ -0,0 +1,39 @@
# sv create
Sets up a new SvelteKit project with optional additional functionality.
## Usage
```sh
npx sv create [options] [path]
```
## Options
### `--from-playground <url>`
Create project from a [playground](/playground) URL. Downloads files, detects dependencies, and sets up complete project structure.
```sh
npx sv create --from-playground="https://svelte.dev/playground/hello-world"
```
### `--template <name>`
- `minimal` — barebones scaffolding
- `demo` — showcase app with word guessing game (works without JS)
- `library` — Svelte library template with `svelte-package`
### `--types <option>`
- `ts``.ts` files and `lang="ts"` in `.svelte` components
- `jsdoc` — [JSDoc syntax](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) for types
### `--no-types`
Disable typechecking (not recommended)
### `--no-add-ons`
Skip interactive add-ons prompt
### `--install <package-manager>`
Install dependencies with: `npm`, `pnpm`, `yarn`, `bun`, or `deno`
### `--no-install`
Skip dependency installation

View File

@@ -0,0 +1,39 @@
# sv migrate
CLI tool for migrating Svelte(Kit) codebases. Delegates to [`svelte-migrate`](https://www.npmjs.com/package/svelte-migrate).
May add `@migration` task annotations in code for manual completion.
## Usage
```sh
npx sv migrate
```
Or specify migration:
```sh
npx sv migrate [migration]
```
## Migrations
### `app-state`
Migrates `$app/stores``$app/state` in `.svelte` files. [Details](/docs/kit/migrating-to-sveltekit-2#SvelteKit-2.12:-$app-stores-deprecated)
### `svelte-5`
Upgrades Svelte 4 → 5, converts components to [runes](../svelte/what-are-runes) syntax. [Migration guide](../svelte/v5-migration-guide)
### `self-closing-tags`
Replaces self-closing non-void elements. [PR](https://github.com/sveltejs/kit/pull/12128)
### `svelte-4`
Upgrades Svelte 3 → 4. [Migration guide](../svelte/v4-migration-guide)
### `sveltekit-2`
Upgrades SvelteKit 1 → 2. [Migration guide](../kit/migrating-to-sveltekit-2)
### `package`
Upgrades `@sveltejs/package` v1 → v2. [PR](https://github.com/sveltejs/kit/pull/8922)
### `routes`
Upgrades pre-release SvelteKit to v1 filesystem routing. [Discussion](https://github.com/sveltejs/kit/discussions/5774)

View File

@@ -0,0 +1,24 @@
# SvelteKit Adapters
Adapters enable deployment to various platforms.
## Usage
```sh
npx sv add sveltekit-adapter
```
Installs and configures chosen adapter in `svelte.config.js`.
## Adapter Options
- `auto` — [`@sveltejs/adapter-auto`](/docs/kit/adapter-auto) - auto-selects adapter, less configurable
- `node` — [`@sveltejs/adapter-node`](/docs/kit/adapter-node) - standalone Node server
- `static` — [`@sveltejs/adapter-static`](/docs/kit/adapter-static) - static site generator (SSG)
- `vercel` — [`@sveltejs/adapter-vercel`](/docs/kit/adapter-vercel) - Vercel deployment
- `cloudflare` — [`@sveltejs/adapter-cloudflare`](/docs/kit/adapter-cloudflare) - Cloudflare deployment
- `netlify` — [`@sveltejs/adapter-netlify`](/docs/kit/adapter-netlify) - Netlify deployment
```sh
npx sv add sveltekit-adapter=adapter:node
```

View File

@@ -0,0 +1,26 @@
# Tailwind CSS
## Setup
```sh
npx sv add tailwindcss
```
## What's Installed
- Tailwind config following SvelteKit guide
- Tailwind Vite plugin
- Updated `app.css` and `+layout.svelte` (SvelteKit) or `App.svelte` (Vite)
- Prettier integration (if installed)
## Options
Add plugins via CLI:
```sh
npx sv add tailwindcss="plugins:typography"
```
Available plugins:
- `typography` — [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography)
- `forms` — [@tailwindcss/forms](https://github.com/tailwindlabs/tailwindcss-forms)

View File

@@ -0,0 +1,11 @@
# Vitest
Vite-native testing framework for SvelteKit.
## Setup
```sh
npx sv add vitest
```
Installs packages, adds scripts to `package.json`, configures client/server-aware Svelte testing in Vite config, and includes demo tests.

View File

@@ -0,0 +1,33 @@
# $app/environment
```js
import { browser, building, dev, version } from '$app/environment';
```
## browser
`true` if app is running in the browser.
```dts
const browser: boolean;
```
## building
`true` during the build step and prerendering.
```dts
const building: boolean;
```
## dev
Whether dev server is running. Not guaranteed to match `NODE_ENV` or `MODE`.
```dts
const dev: boolean;
```
## version
The value of `config.kit.version.name`.
```dts
const version: string;
```

View File

@@ -0,0 +1,75 @@
# $app/forms
```js
import { applyAction, deserialize, enhance } from '$app/forms';
```
## applyAction
Updates `form` property and `page.status` of current page with given data. Redirects to nearest error page on error.
```dts
function applyAction<
Success extends Record<string, unknown> | undefined,
Failure extends Record<string, unknown> | undefined
>(
result: import('@sveltejs/kit').ActionResult<Success, Failure>
): Promise<void>;
```
## deserialize
Deserializes form submission response.
```js
// @errors: 7031
import { deserialize } from '$app/forms';
async function handleSubmit(event) {
const response = await fetch('/form?/action', {
method: 'POST',
body: new FormData(event.target)
});
const result = deserialize(await response.text());
// ...
}
```
```dts
function deserialize<
Success extends Record<string, unknown> | undefined,
Failure extends Record<string, unknown> | undefined
>(
result: string
): import('@sveltejs/kit').ActionResult<Success, Failure>;
```
## enhance
Progressive enhancement for `<form>` elements.
**`submit` function**: Called on submission with FormData and `action`. Call `cancel` to prevent submission. Use abort `controller` to cancel if another submission starts. Return a function to handle server response, or return nothing for default behavior.
**Default behavior**:
- Updates `form` prop if action is on same page
- Updates `page.status`
- Resets form and invalidates all data on successful submission (no redirect)
- Redirects on redirect response
- Redirects to error page on unexpected error
**Custom callback**: Call `update` for default behavior with options:
- `reset: false` - don't reset form values after success
- `invalidateAll: false` - don't call `invalidateAll` after submission
```dts
function enhance<
Success extends Record<string, unknown> | undefined,
Failure extends Record<string, unknown> | undefined
>(
form_element: HTMLFormElement,
submit?: import('@sveltejs/kit').SubmitFunction<Success, Failure>
): {
destroy(): void;
};
```

View File

@@ -0,0 +1,160 @@
# $app/navigation
```js
import {
afterNavigate,
beforeNavigate,
disableScrollHandling,
goto,
invalidate,
invalidateAll,
onNavigate,
preloadCode,
preloadData,
pushState,
refreshAll,
replaceState
} from '$app/navigation';
```
## afterNavigate
Runs callback when component mounts and on every navigation. Must be called during component initialization. Active while component is mounted.
```dts
function afterNavigate(
callback: (navigation: import('@sveltejs/kit').AfterNavigate) => void
): void;
```
## beforeNavigate
Intercepts navigation before it happens (links, `goto()`, browser back/forward). Call `cancel()` to prevent navigation. For `'leave'` navigations (closing tab/navigating away), `cancel()` triggers native browser confirmation dialog.
- `navigation.to.route.id` is `null` for non-SvelteKit routes
- `navigation.willUnload` is `true` for `'leave'` navigations and `'link'` navigations where `navigation.to.route === null`
Must be called during component initialization. Active while component is mounted.
```dts
function beforeNavigate(
callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void
): void;
```
## disableScrollHandling
Disables SvelteKit's built-in scroll handling when called during page update (in `onMount`, `afterNavigate`, or actions). Discouraged as it breaks user expectations.
```dts
function disableScrollHandling(): void;
```
## goto
Navigate programmatically. Returns Promise that resolves on navigation completion. For external URLs, use `window.location = url` instead.
```dts
function goto(
url: string | URL,
opts?: {
replaceState?: boolean | undefined;
noScroll?: boolean | undefined;
keepFocus?: boolean | undefined;
invalidateAll?: boolean | undefined;
invalidate?: (string | URL | ((url: URL) => boolean))[] | undefined;
state?: App.PageState | undefined;
}
): Promise<void>;
```
## invalidate
Re-runs `load` functions that depend on the URL (via `fetch` or `depends`). Returns Promise that resolves when page updates.
- String/URL must match exactly what was passed to `fetch`/`depends` (including query params)
- Custom identifiers: use format `[a-z]+:` (e.g. `custom:state`)
- Function predicate receives full URL, re-runs if returns `true`
```ts
// Match '/path' regardless of query parameters
import { invalidate } from '$app/navigation';
invalidate((url) => url.pathname === '/path');
```
```dts
function invalidate(
resource: string | URL | ((url: URL) => boolean)
): Promise<void>;
```
## invalidateAll
Re-runs all `load` functions for current page. Returns Promise that resolves when page updates.
```dts
function invalidateAll(): Promise<void>;
```
## onNavigate
Runs callback immediately before navigation (except full-page navigations). Return Promise to delay navigation completion (useful for `document.startViewTransition`). Avoid slow promises as navigation appears stalled.
Returning a function (or Promise resolving to function) calls it after DOM updates.
Must be called during component initialization. Active while component is mounted.
```dts
function onNavigate(
callback: (navigation: import('@sveltejs/kit').OnNavigate) =>
MaybePromise<(() => void) | void>
): void;
```
## preloadCode
Imports code for routes not yet fetched. Specify routes by pathname: `/about` or `/blog/*`. Doesn't call `load` functions. Returns Promise resolving when modules imported.
```dts
function preloadCode(pathname: string): Promise<void>;
```
## preloadData
Preloads page: imports code and calls `load` functions. Same as hovering/tapping `<a>` with `data-sveltekit-preload-data`. Makes navigation instantaneous if next navigation is to `href`. Returns Promise with `load` results.
```dts
function preloadData(href: string): Promise<
| { type: 'loaded'; status: number; data: Record<string, any>; }
| { type: 'redirect'; location: string; }
>;
```
## pushState
Creates new history entry with given `page.state`. Pass `''` for current URL. Used for shallow routing.
```dts
function pushState(url: string | URL, state: App.PageState): void;
```
## refreshAll
Refreshes all active remote functions and re-runs `load` functions (unless disabled). Returns Promise resolving when page updates.
```dts
function refreshAll({
includeLoadFunctions
}?: {
includeLoadFunctions?: boolean;
}): Promise<void>;
```
## replaceState
Replaces current history entry with given `page.state`. Pass `''` for current URL. Used for shallow routing.
```dts
function replaceState(url: string | URL, state: App.PageState): void;
```

View File

@@ -0,0 +1,54 @@
# $app/paths
```js
import { asset, assets, base, resolve, resolveRoute } from '$app/paths';
```
## asset
Resolve URL of an asset in `static` directory by prefixing with `config.kit.paths.assets` or base path.
During SSR, base path is relative and depends on current page.
```svelte
<script>
import { asset } from '$app/paths';
</script>
<img alt="a potato" src={asset('/potato.jpg')} />
```
```dts
function asset(file: Asset): string;
```
## resolve
Resolve pathname by prefixing with base path, or resolve route ID by populating dynamic segments with parameters.
During SSR, base path is relative and depends on current page.
```js
// @errors: 7031
import { resolve } from '$app/paths';
// using a pathname
const resolved = resolve(`/blog/hello-world`);
// using a route ID plus parameters
const resolved = resolve('/blog/[slug]', {
slug: 'hello-world'
});
```
```dts
function resolve<T extends RouteId | Pathname>(
...args: ResolveArgs<T>
): ResolvedPathname;
```
## Deprecated
- **assets**: Use `asset(...)` instead. Absolute path matching `config.kit.paths.assets`.
- **base**: Use `resolve(...)` instead. String matching `config.kit.paths.base`.
- **resolveRoute**: Use `resolve(...)` instead.

View File

@@ -0,0 +1,148 @@
# $app/server
```js
import {
command,
form,
getRequestEvent,
prerender,
query,
read
} from '$app/server';
```
## command
*Available since 2.27*
Creates a remote command. When called from browser, invokes function on server via `fetch`.
```dts
function command<Output>(fn: () => Output): RemoteCommand<void, Output>;
function command<Input, Output>(
validate: 'unchecked',
fn: (arg: Input) => Output
): RemoteCommand<Input, Output>;
function command<Schema extends StandardSchemaV1, Output>(
validate: Schema,
fn: (arg: StandardSchemaV1.InferOutput<Schema>) => Output
): RemoteCommand<StandardSchemaV1.InferInput<Schema>, Output>;
```
See [Remote functions](/docs/kit/remote-functions#command).
## form
*Available since 2.27*
Creates a form object that can be spread onto a `<form>` element.
```dts
function form<Output>(
fn: (invalid: Invalid<void>) => MaybePromise<Output>
): RemoteForm<void, Output>;
function form<Input extends RemoteFormInput, Output>(
validate: 'unchecked',
fn: (data: Input, invalid: Invalid<Input>) => MaybePromise<Output>
): RemoteForm<Input, Output>;
function form<Schema extends StandardSchemaV1<RemoteFormInput, Record<string, any>>, Output>(
validate: Schema,
fn: (data: StandardSchemaV1.InferOutput<Schema>, invalid: Invalid<StandardSchemaV1.InferOutput<Schema>>) => MaybePromise<Output>
): RemoteForm<StandardSchemaV1.InferInput<Schema>, Output>;
```
See [Remote functions](/docs/kit/remote-functions#form).
## getRequestEvent
*Available since 2.20.0*
Returns current `RequestEvent`. Use in server hooks, server `load` functions, actions, and endpoints.
**Important:** In environments without `AsyncLocalStorage`, must be called synchronously (not after `await`).
```dts
function getRequestEvent(): RequestEvent;
```
## prerender
*Available since 2.27*
Creates a remote prerender function. When called from browser, invokes function on server via `fetch`.
```dts
function prerender<Output>(
fn: () => MaybePromise<Output>,
options?: { inputs?: RemotePrerenderInputsGenerator<void>; dynamic?: boolean; }
): RemotePrerenderFunction<void, Output>;
function prerender<Input, Output>(
validate: 'unchecked',
fn: (arg: Input) => MaybePromise<Output>,
options?: { inputs?: RemotePrerenderInputsGenerator<Input>; dynamic?: boolean; }
): RemotePrerenderFunction<Input, Output>;
function prerender<Schema extends StandardSchemaV1, Output>(
schema: Schema,
fn: (arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>,
options?: { inputs?: RemotePrerenderInputsGenerator<StandardSchemaV1.InferInput<Schema>>; dynamic?: boolean; }
): RemotePrerenderFunction<StandardSchemaV1.InferInput<Schema>, Output>;
```
See [Remote functions](/docs/kit/remote-functions#prerender).
## query
*Available since 2.27*
Creates a remote query. When called from browser, invokes function on server via `fetch`.
```dts
function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output>;
function query<Input, Output>(
validate: 'unchecked',
fn: (arg: Input) => MaybePromise<Output>
): RemoteQueryFunction<Input, Output>;
function query<Schema extends StandardSchemaV1, Output>(
schema: Schema,
fn: (arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>
): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>;
```
### query.batch
*Available since 2.35*
Collects multiple calls and executes them in a single request.
```dts
function batch<Input, Output>(
validate: 'unchecked',
fn: (args: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>
): RemoteQueryFunction<Input, Output>;
function batch<Schema extends StandardSchemaV1, Output>(
schema: Schema,
fn: (args: StandardSchemaV1.InferOutput<Schema>[]) => MaybePromise<(arg: StandardSchemaV1.InferOutput<Schema>, idx: number) => Output>
): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>;
```
See [Remote functions](/docs/kit/remote-functions#query) and [query.batch](/docs/kit/remote-functions#query.batch).
## read
*Available since 2.4.0*
Reads contents of an imported asset from filesystem.
```js
import { read } from '$app/server';
import somefile from './somefile.txt';
const asset = read(somefile);
const text = await asset.text();
```
```dts
function read(asset: string): Response;
```

View File

@@ -0,0 +1,78 @@
# $app/state
Three read-only state objects for SvelteKit apps. Added in 2.12 (use `$app/stores` for earlier versions).
```js
import { navigating, page, updated } from '$app/state';
```
## navigating
Read-only object representing in-progress navigation. `null` when no navigation occurring or during SSR.
```dts
const navigating:
| import('@sveltejs/kit').Navigation
| {
from: null;
to: null;
type: null;
willUnload: null;
delta: null;
complete: null;
};
```
Properties: `from`, `to`, `type`, and `delta` (if `type === 'popstate'`).
## page
Read-only reactive object with current page info:
- Combined `data` from all pages/layouts
- Current `form` prop value
- Page state from `goto`, `pushState`, `replaceState`
- Metadata: URL, route, params, error status
```svelte
<!--- file: +layout.svelte --->
<script>
import { page } from '$app/state';
</script>
<p>Currently at {page.url.pathname}</p>
{#if page.error}
<span class="red">Problem detected</span>
{:else}
<span class="small">All systems operational</span>
{/if}
```
**Important:** Changes only work with runes, not legacy reactivity:
```svelte
<!--- file: +page.svelte --->
<script>
import { page } from '$app/state';
const id = $derived(page.params.id); // ✓ Updates correctly
$: badId = page.params.id; // ✗ Never updates after initial load
</script>
```
Server: read only during rendering (not in `load` functions).
Browser: read anytime.
```dts
const page: import('@sveltejs/kit').Page;
```
## updated
Read-only reactive value, initially `false`. Becomes `true` when new app version detected (if `version.pollInterval` configured). `updated.check()` forces immediate check.
```dts
const updated: {
get current(): boolean;
check(): Promise<boolean>;
};
```

View File

@@ -0,0 +1,63 @@
# $app/stores
**DEPRECATED**: Use `$app/state` instead (SvelteKit 2.12+). Store-based equivalents for legacy support.
```js
import { getStores, navigating, page, updated } from '$app/stores';
```
## getStores
```dts
function getStores(): {
page: typeof page;
navigating: typeof navigating;
updated: typeof updated;
};
```
## navigating
**Readable store** for navigation state.
- **During navigation**: `Navigation` object with `from`, `to`, `type`, and `delta` (if `type === 'popstate'`)
- **When idle**: `null`
**Server**: Subscribe only during component initialization
**Browser**: Subscribe anytime
```dts
const navigating: import('svelte/store').Readable<
import('@sveltejs/kit').Navigation | null
>;
```
## page
**Readable store** containing page data.
**Server**: Subscribe only during component initialization
**Browser**: Subscribe anytime
```dts
const page: import('svelte/store').Readable<
import('@sveltejs/kit').Page
>;
```
## updated
**Readable store** for app version updates.
- Initial value: `false`
- Becomes `true` when new version detected (if `version.pollInterval` configured)
- `updated.check()`: Force immediate version check
**Server**: Subscribe only during component initialization
**Browser**: Subscribe anytime
```dts
const updated: import('svelte/store').Readable<boolean> & {
check(): Promise<boolean>;
};
```

View File

@@ -0,0 +1,59 @@
# $app/types
Generated types for routes in your app (available since SvelteKit 2.26).
```js
import type { RouteId, RouteParams, LayoutParams } from '$app/types';
```
## Asset
Union of all filenames in `static` directory + wildcard for imported asset paths.
```dts
type Asset = '/favicon.png' | '/robots.txt' | (string & {});
```
## RouteId
Union of all route IDs. Used for `page.route.id` and `event.route.id`.
```dts
type RouteId = '/' | '/my-route' | '/my-other-route/[param]';
```
## Pathname
Union of all valid pathnames.
```dts
type Pathname = '/' | '/my-route' | `/my-other-route/${string}` & {};
```
## ResolvedPathname
Like `Pathname` but possibly prefixed with base path. Used for `page.url.pathname`.
```dts
type ResolvedPathname = `${'' | `/${string}`}/` | `${'' | `/${string}`}/my-route` | `${'' | `/${string}`}/my-other-route/${string}` | {};
```
## RouteParams
Get parameters for a route.
```ts
type BlogParams = RouteParams<'/blog/[slug]'>; // { slug: string }
```
```dts
type RouteParams<T extends RouteId> = { /* generated */ } | Record<string, never>;
```
## LayoutParams
Get parameters for a layout, including optional parameters from child routes.
```dts
type RouteParams<T extends RouteId> = { /* generated */ } | Record<string, never>;
```

View File

@@ -0,0 +1,15 @@
# $env/dynamic/private
Access to runtime environment variables from the platform (e.g., `process.env` in adapter-node).
**Rules:**
- Only includes variables that do NOT start with `config.kit.env.publicPrefix`
- Only includes variables that DO start with `config.kit.env.privatePrefix` (if configured)
- **Cannot be imported in client-side code**
```ts
import { env } from '$env/dynamic/private';
console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
```
**Note:** In dev, always includes `.env` variables. In prod, depends on adapter.

View File

@@ -0,0 +1,13 @@
# $env/dynamic/public
Runtime access to public environment variables (prefixed with `PUBLIC_` by default via `config.kit.env.publicPrefix`).
**Key differences:**
- Only includes variables with public prefix (safe for client-side)
- All public dynamic env vars sent server→client (increases network payload)
- **Prefer `$env/static/public`** when possible to avoid larger requests
```ts
import { env } from '$env/dynamic/public';
console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
```

View File

@@ -0,0 +1,29 @@
# $env/static/private
Server-only module for private environment variables loaded from `.env` files and `process.env`.
**Key characteristics:**
- Cannot be imported in client-side code
- Includes variables that do NOT begin with `config.kit.env.publicPrefix`
- Optionally filtered by `config.kit.env.privatePrefix`
- Values are **statically injected at build time** (enables dead code elimination)
## Usage
```ts
import { API_KEY } from '$env/static/private';
```
## Best practices
Declare all referenced env vars (even if empty) in `.env`:
```
MY_FEATURE_FLAG=""
```
Override from command line:
```sh
MY_FEATURE_FLAG="enabled" npm run dev
```

View File

@@ -0,0 +1,7 @@
# $env/static/public
Environment variables with `PUBLIC_` prefix (configurable via `config.kit.env.publicPrefix`). Safe for client-side code. Values replaced at build time.
```ts
import { PUBLIC_BASE_URL } from '$env/static/public';
```

View File

@@ -0,0 +1,17 @@
# $lib Import Alias
SvelteKit provides `$lib` alias for importing from `src/lib`. Configure via [config file](configuration#files).
```svelte
<!--- file: src/lib/Component.svelte --->
A reusable component
```
```svelte
<!--- file: src/routes/+page.svelte --->
<script>
import Component from '$lib/Component.svelte';
</script>
<Component />
```

View File

@@ -0,0 +1,49 @@
# $service-worker
```js
import { base, build, files, prerendered, version } from '$service-worker';
```
Only available to [service workers](/docs/kit/service-workers).
## base
```ts
const base: string;
```
Base path of deployment. Equivalent to `config.kit.paths.base`, calculated from `location.pathname`. Works correctly in subdirectories.
Note: No `assets` export since service workers can't be used if `config.kit.paths.assets` is specified.
## build
```ts
const build: string[];
```
Array of URLs for Vite-generated files. Use with `cache.addAll(build)`. Empty during development.
## files
```ts
const files: string[];
```
Array of URLs from static directory (`config.kit.files.assets`). Customize via [`config.kit.serviceWorker.files`](/docs/kit/configuration#serviceWorker).
## prerendered
```ts
const prerendered: string[];
```
Array of prerendered page/endpoint pathnames. Empty during development.
## version
```ts
const version: string;
```
From [`config.kit.version`](/docs/kit/configuration#version). Use for unique cache names to invalidate old caches on deployment.

View File

@@ -0,0 +1,81 @@
# sequence
Helper for chaining multiple `handle` functions in middleware-like manner.
```js
import { sequence } from '@sveltejs/kit/hooks';
```
## Behavior
- `transformPageChunk`: applied in **reverse order**, merged
- `preload`: applied in **forward order**, first wins (subsequent calls skipped)
- `filterSerializedResponseHeaders`: same as `preload` (first wins)
## Example
```js
/// file: src/hooks.server.js
import { sequence } from '@sveltejs/kit/hooks';
/** @type {import('@sveltejs/kit').Handle} */
async function first({ event, resolve }) {
console.log('first pre-processing');
const result = await resolve(event, {
transformPageChunk: ({ html }) => {
// transforms are applied in reverse order
console.log('first transform');
return html;
},
preload: () => {
// this one wins as it's the first defined in the chain
console.log('first preload');
return true;
}
});
console.log('first post-processing');
return result;
}
/** @type {import('@sveltejs/kit').Handle} */
async function second({ event, resolve }) {
console.log('second pre-processing');
const result = await resolve(event, {
transformPageChunk: ({ html }) => {
console.log('second transform');
return html;
},
preload: () => {
console.log('second preload');
return true;
},
filterSerializedResponseHeaders: () => {
// this one wins as it's the first defined in the chain
console.log('second filterSerializedResponseHeaders');
return true;
}
});
console.log('second post-processing');
return result;
}
export const handle = sequence(first, second);
```
**Output:**
```
first pre-processing
first preload
second pre-processing
second filterSerializedResponseHeaders
second transform
first transform
second post-processing
first post-processing
```
## Type
```dts
function sequence(...handlers: Handle[]): Handle;
```

View File

@@ -0,0 +1,11 @@
# installPolyfills
Makes web APIs available as globals: `crypto`, `File`
```js
import { installPolyfills } from '@sveltejs/kit/node/polyfills';
```
```dts
function installPolyfills(): void;
```

View File

@@ -0,0 +1,40 @@
# @sveltejs/kit/node
Node.js adapter utilities for converting between Node.js and Web APIs.
## createReadableStream
Converts a file on disk to a readable stream.
```ts
function createReadableStream(file: string): ReadableStream;
```
*Available since 2.4.0*
## getRequest
Converts Node.js `IncomingMessage` to Web `Request`.
```ts
function getRequest({
request,
base,
bodySizeLimit
}: {
request: import('http').IncomingMessage;
base: string;
bodySizeLimit?: number;
}): Promise<Request>;
```
## setResponse
Converts Web `Response` to Node.js `ServerResponse`.
```ts
function setResponse(
res: import('http').ServerResponse,
response: Response
): Promise<void>;
```

View File

@@ -0,0 +1,11 @@
# sveltekit
Returns the SvelteKit Vite plugins.
```js
import { sveltekit } from '@sveltejs/kit/vite';
```
```ts
function sveltekit(): Promise<import('vite').Plugin[]>;
```

View File

@@ -0,0 +1,418 @@
# @sveltejs/kit Server APIs
## Core Functions
### error
Throws HTTP error with status code and optional message. Stops request handling and returns error response.
```ts
function error(status: number, body: App.Error): never;
function error(status: number, body?: { message: string }): never;
```
**Important**: Don't catch the thrown error - it prevents SvelteKit from handling it.
### redirect
Redirects a request. Common status codes:
- `303`: GET redirect (often after form POST)
- `307`: Temporary redirect (keeps method)
- `308`: Permanent redirect (keeps method, SEO transfer)
```ts
function redirect(
status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308,
location: string | URL
): never;
```
**Important**: Don't catch the thrown redirect.
### fail
Creates `ActionFailure` for failed form submissions.
```ts
function fail(status: number): ActionFailure<undefined>;
function fail<T>(status: number, data: T): ActionFailure<T>;
```
### json / text
Create Response objects:
```ts
function json(data: any, init?: ResponseInit): Response;
function text(body: string, init?: ResponseInit): Response;
```
### normalizeUrl
Strips SvelteKit-internal suffixes and trailing slashes. Available since 2.18.0.
```ts
const { url, denormalize } = normalizeUrl('/blog/post/__data.json');
console.log(url.pathname); // /blog/post
console.log(denormalize('/blog/post/a')); // /blog/post/a/__data.json
```
## Type Checkers
```ts
function isActionFailure(e: unknown): e is ActionFailure;
function isHttpError(e: unknown, status?: number): e is HttpError;
function isRedirect(e: unknown): e is Redirect;
```
## Server Class
```ts
class Server {
constructor(manifest: SSRManifest);
init(options: ServerInitOptions): Promise<void>;
respond(request: Request, options: RequestOptions): Promise<Response>;
}
```
## Hooks
### Handle
Runs on every request. Receives `event` and `resolve` function.
```ts
type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<Response>;
```
### HandleFetch
Modifies `event.fetch()` calls on server/during prerendering.
```ts
type HandleFetch = (input: {
event: RequestEvent;
request: Request;
fetch: typeof fetch;
}) => MaybePromise<Response>;
```
### HandleError
**Client-side**: Runs on unexpected navigation errors. Must never throw.
```ts
type HandleClientError = (input: {
error: unknown;
event: NavigationEvent;
status: number;
message: string;
}) => MaybePromise<void | App.Error>;
```
**Server-side**: Runs on unexpected request errors. Must never throw.
```ts
type HandleServerError = (input: {
error: unknown;
event: RequestEvent;
status: number;
message: string;
}) => MaybePromise<void | App.Error>;
```
### HandleValidationError
Runs when remote function argument validation fails.
```ts
type HandleValidationError = (input: {
issues: StandardSchemaV1.Issue[];
event: RequestEvent;
}) => MaybePromise<App.Error>;
```
### Reroute
Modifies URL before route determination. Available since 2.3.0.
```ts
type Reroute = (event: {
url: URL;
fetch: typeof fetch;
}) => MaybePromise<void | string>;
```
### Init Hooks
Available since 2.10.0.
```ts
type ClientInit = () => MaybePromise<void>; // Runs when app starts in browser
type ServerInit = () => MaybePromise<void>; // Runs before first request
```
### Transport
Custom type serialization across server/client. Available since 2.11.0.
```ts
type Transport = Record<string, Transporter>;
interface Transporter<T, U> {
encode: (value: T) => false | U;
decode: (data: U) => T;
}
```
## RequestEvent
Core event object for server-side code:
```ts
interface RequestEvent<Params, RouteId> {
cookies: Cookies;
fetch: typeof fetch; // Enhanced with cookie/auth inheritance, relative URLs, direct internal calls
getClientAddress: () => string;
locals: App.Locals;
params: Params;
platform: App.Platform | undefined;
request: Request;
route: { id: RouteId };
setHeaders: (headers: Record<string, string>) => void;
url: URL;
isDataRequest: boolean; // True for +page/layout.server.js data requests
isSubRequest: boolean; // True for same-origin fetch without HTTP overhead
isRemoteRequest: boolean; // True for remote function calls
tracing: { enabled: boolean; root: Span; current: Span }; // Since 2.31.0
}
```
## Cookies
```ts
interface Cookies {
get(name: string, opts?: CookieParseOptions): string | undefined;
getAll(opts?: CookieParseOptions): Array<{ name: string; value: string }>;
set(name: string, value: string, opts: CookieSerializeOptions & { path: string }): void;
delete(name: string, opts: CookieSerializeOptions & { path: string }): void;
serialize(name: string, value: string, opts: CookieSerializeOptions & { path: string }): string;
}
```
**Defaults**: `httpOnly` and `secure` are `true` (except on http://localhost). `sameSite` defaults to `lax`.
**Important**: Must specify `path` (usually `path: '/'`). Can't set `set-cookie` via `setHeaders` - use `cookies` API in server-only `load`.
## Load Functions
### LoadEvent (Client/Universal)
```ts
interface LoadEvent<Params, Data, ParentData, RouteId> {
fetch: typeof fetch; // Enhanced with cookie inheritance, relative URLs, inlining
data: Data; // From server load
params: Params;
route: { id: RouteId };
url: URL;
setHeaders: (headers: Record<string, string>) => void; // No effect in browser
parent: () => Promise<ParentData>;
depends: (...deps: Array<`${string}:${string}`>) => void;
untrack: <T>(fn: () => T) => T;
tracing: { enabled: boolean; root: Span; current: Span }; // Since 2.31.0
}
```
### ServerLoadEvent
Extends `RequestEvent` with:
```ts
interface ServerLoadEvent<Params, ParentData, RouteId> extends RequestEvent<Params, RouteId> {
parent: () => Promise<ParentData>;
depends: (...deps: string[]) => void;
untrack: <T>(fn: () => T) => T;
tracing: { enabled: boolean; root: Span; current: Span }; // Since 2.31.0
}
```
**Gotcha**: Call `parent()` after fetching other data to avoid waterfalls.
## Actions
### Action Type
```ts
type Action<Params, OutputData, RouteId> = (
event: RequestEvent<Params, RouteId>
) => MaybePromise<OutputData>;
type Actions<Params, OutputData, RouteId> = Record<string, Action<Params, OutputData, RouteId>>;
```
### ActionResult
```ts
type ActionResult<Success, Failure> =
| { type: 'success'; status: number; data?: Success }
| { type: 'failure'; status: number; data?: Failure }
| { type: 'redirect'; status: number; location: string }
| { type: 'error'; status?: number; error: any };
```
### SubmitFunction
```ts
type SubmitFunction<Success, Failure> = (input: {
action: URL;
formData: FormData;
formElement: HTMLFormElement;
controller: AbortController;
submitter: HTMLElement | null;
cancel: () => void;
}) => MaybePromise<void | ((opts: {
formData: FormData;
formElement: HTMLFormElement;
action: URL;
result: ActionResult<Success, Failure>;
update: (options?: { reset?: boolean; invalidateAll?: boolean }) => Promise<void>;
}) => MaybePromise<void>)>;
```
## Remote Functions
### RemoteQuery
```ts
type RemoteQuery<T> = RemoteResource<T> & {
set(value: T): void;
refresh(): Promise<void>;
withOverride(update: (current: Awaited<T>) => Awaited<T>): RemoteQueryOverride;
};
type RemoteQueryFunction<Input, Output> = (arg: Input) => RemoteQuery<Output>;
```
### RemoteCommand
```ts
type RemoteCommand<Input, Output> = {
(arg: Input): Promise<Awaited<Output>> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<Awaited<Output>>;
};
get pending(): number;
};
```
### RemoteForm
```ts
type RemoteForm<Input, Output> = {
[attachment: symbol]: (node: HTMLFormElement) => void;
method: 'POST';
action: string;
enhance(callback: (opts: { form: HTMLFormElement; data: Input; submit: () => Promise<void> }) => void): { ... };
for(id: ExtractId<Input>): Omit<RemoteForm<Input, Output>, 'for'>;
preflight(schema: StandardSchemaV1<Input, any>): RemoteForm<Input, Output>;
validate(options?: { includeUntouched?: boolean; submitter?: HTMLButtonElement }): Promise<void>;
get result(): Output | undefined;
get pending(): number;
fields: RemoteFormFields<Input>;
buttonProps: { ... };
};
```
### RemotePrerenderFunction
```ts
type RemotePrerenderFunction<Input, Output> = (arg: Input) => RemoteResource<Output>;
```
### RemoteResource
```ts
type RemoteResource<T> = Promise<Awaited<T>> & {
get error(): any;
get loading(): boolean;
} & ({ get current(): undefined; ready: false } | { get current(): Awaited<T>; ready: true });
```
## Navigation Types
### NavigationType
```ts
type NavigationType = 'enter' | 'form' | 'leave' | 'link' | 'goto' | 'popstate';
```
### AfterNavigate
```ts
type AfterNavigate = (Navigation | NavigationEnter) & {
type: Exclude<NavigationType, 'leave'>;
willUnload: false;
};
```
### BeforeNavigate
```ts
type BeforeNavigate = Navigation & {
cancel: () => void;
};
```
### OnNavigate
```ts
type OnNavigate = Navigation & {
type: Exclude<NavigationType, 'enter' | 'leave'>;
willUnload: false;
};
```
## Page
```ts
interface Page<Params, RouteId> {
url: URL & { pathname: ResolvedPathname };
params: Params;
route: { id: RouteId };
status: number;
error: App.Error | null;
data: App.PageData & Record<string, any>;
state: App.PageState;
form: any; // Filled after form submission
}
```
## Adapter
```ts
interface Adapter {
name: string;
adapt: (builder: Builder) => MaybePromise<void>;
supports?: {
read?: (details: { config: any; route: { id: string } }) => boolean;
instrumentation?: () => boolean; // Since 2.31.0
};
emulate?: () => MaybePromise<Emulator>;
}
```
### Builder
Key methods:
- `writeClient(dest: string): string[]`
- `writeServer(dest: string): string[]`
- `writePrerendered(dest: string): string[]`
- `generateManifest(opts: { relativePath: string; routes?: RouteDefinition[] }): string`
- `compress(directory: string): Promise<void>`
- `instrument(args: { entrypoint: string; instrumentation: string; ... }): void` (Since 2.31.0)
## Snapshot
```ts
interface Snapshot<T> {
capture: () => T;
restore: (snapshot: T) => void;
}
```
## Constants
```ts
const VERSION: string; // SvelteKit version
```

View File

@@ -0,0 +1,73 @@
# Accessibility
SvelteKit provides accessible defaults. You're responsible for ensuring your application code is accessible.
## Route Announcements
SvelteKit injects a [live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) that announces page changes during client-side navigation by reading the `<title>` element.
**Every page needs a unique, descriptive title:**
```svelte
<!--- file: src/routes/+page.svelte --->
<svelte:head>
<title>Todo List</title>
</svelte:head>
```
## Focus Management
After each navigation and enhanced form submission, SvelteKit focuses `<body>` (or an element with `autofocus` if present).
**Customize focus with `afterNavigate`:**
```js
import { afterNavigate } from '$app/navigation';
afterNavigate(() => {
/** @type {HTMLElement | null} */
const to_focus = document.querySelector('.focus-me');
to_focus?.focus();
});
```
**`goto` with `keepFocus` option:** Preserves current focus instead of resetting. Ensure the focused element still exists after navigation.
## Lang Attribute
Default is English. Update `src/app.html` for other languages:
```html
/// file: src/app.html
<html lang="de">
```
**For multiple languages, use a placeholder and `handle` hook:**
```html
/// file: src/app.html
<html lang="%lang%">
```
```js
/// file: src/hooks.server.js
/**
* @param {import('@sveltejs/kit').RequestEvent} event
*/
function get_lang(event) {
return 'en';
}
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export function handle({ event, resolve }) {
return resolve(event, {
transformPageChunk: ({ html }) => html.replace('%lang%', get_lang(event))
});
}
```
## Further Reading
- [MDN Web Docs: Accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility)
- [The A11y Project](https://www.a11yproject.com/)
- [How to Meet WCAG](https://www.w3.org/WAI/WCAG21/quickref/)

View File

@@ -0,0 +1,20 @@
# adapter-auto
`adapter-auto` is installed by default with `npx sv create`. It automatically detects and uses the correct adapter for supported platforms at deploy time:
- `@sveltejs/adapter-cloudflare` - Cloudflare Pages
- `@sveltejs/adapter-netlify` - Netlify
- `@sveltejs/adapter-vercel` - Vercel
- `svelte-adapter-azure-swa` - Azure Static Web Apps
- `svelte-kit-sst` - AWS via SST
- `@sveltejs/adapter-node` - Google Cloud Run
**Best practice:** Install the specific adapter to `devDependencies` once you've chosen a platform for better lockfile management and faster CI installs.
## Configuration
`adapter-auto` accepts no options. To configure adapter-specific options (e.g., `{ edge: true }` for Vercel/Netlify), install and use the underlying adapter directly.
## Extending
Add support for more adapters by editing [adapters.js](https://github.com/sveltejs/kit/blob/main/packages/adapter-auto/adapters.js) and submitting a PR.

View File

@@ -0,0 +1,114 @@
# adapter-cloudflare-workers
> **DEPRECATED**: Use [`adapter-cloudflare`](adapter-cloudflare) instead. Cloudflare Workers Sites is being deprecated in favor of Workers with Static Assets.
Deploys to [Cloudflare Workers](https://workers.cloudflare.com/) with [Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/).
## Installation
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare-workers';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
// see options below
})
}
};
export default config;
```
## Options
- **`config`**: Path to Wrangler config file (if not using default `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml`)
- **`platformProxy`**: Preferences for emulated `platform.env` local bindings. See [getPlatformProxy docs](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1)
## Configuration
Create Wrangler config in project root:
```jsonc
/// file: wrangler.jsonc
{
"name": "<your-service-name>",
"account_id": "<your-account-id>",
"main": "./.cloudflare/worker.js",
"site": {
"bucket": "./.cloudflare/public"
},
"build": {
"command": "npm run build"
},
"compatibility_date": "2021-11-12"
}
```
Get `<your-account-id>` from `wrangler whoami` or Cloudflare dashboard URL.
Add `.cloudflare` and `.wrangler` to `.gitignore`.
## Deploy
```sh
npm i -D wrangler
wrangler login
wrangler deploy
```
## Runtime APIs
Access Cloudflare bindings (KV, DO, etc.) via `platform` in hooks and endpoints:
```js
/// file: +server.js
/** @type {import('./$types').RequestHandler} */
export async function POST({ request, platform }) {
const x = platform?.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x');
}
```
**Type definitions:**
```ts
/// file: src/app.d.ts
import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types';
declare global {
namespace App {
interface Platform {
env?: {
YOUR_KV_NAMESPACE: KVNamespace;
YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
};
}
}
}
export {};
```
Use SvelteKit's `$env` module for environment variables.
## Local Testing
`platform` values are emulated in dev/preview using Wrangler config bindings. Configure via `platformProxy` option.
For testing builds, use Wrangler v4: `wrangler dev`
## Troubleshooting
**Node.js compatibility**: Add to Wrangler config:
```jsonc
/// file: wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat"]
}
```
**Worker size limits**: If bundle exceeds [size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size), import large libraries client-side only.
**File system**: `fs` unavailable — [prerender](page-options#prerender) routes that need it.

View File

@@ -0,0 +1,209 @@
# Cloudflare Adapter
Deploy to Cloudflare Workers or Cloudflare Pages using `adapter-cloudflare`.
## Comparisons
- `adapter-cloudflare` all SvelteKit features; builds for Workers Static Assets and Pages
- `adapter-cloudflare-workers` deprecated
- `adapter-static` client-side only; compatible with Workers Static Assets and Pages
## Usage
```bash
npm i -D @sveltejs/adapter-cloudflare
```
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
config: undefined,
platformProxy: {
configPath: undefined,
environment: undefined,
persist: undefined
},
fallback: 'plaintext',
routes: {
include: ['/*'],
exclude: ['<all>']
}
})
}
};
export default config;
```
## Options
### config
Path to Wrangler config file (if not using default `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml`).
### platformProxy
Preferences for emulated `platform.env` local bindings. See [getPlatformProxy docs](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1).
### fallback
Render `plaintext` or `spa` 404 page for non-matching assets. Default `plaintext` is usually sufficient. Use `spa` if using `routes.exclude` to avoid unstyled 404s.
### routes
**Cloudflare Pages only.** Customizes `_routes.json`:
- `include` routes that invoke a function (default `['/*']`)
- `exclude` routes that don't invoke a function (faster/cheaper for static assets)
- `<build>` Vite build artifacts
- `<files>` `static` directory contents
- `<prerendered>` prerendered pages
- `<all>` all above (default)
Max 100 combined rules. Useful if `<prerendered>` exceeds limit (e.g., use `'/articles/*'` instead of individual paths).
## Cloudflare Workers
### Basic Configuration
```jsonc
/// file: wrangler.jsonc
{
"name": "<any-name-you-want>",
"main": ".svelte-kit/cloudflare/_worker.js",
"compatibility_date": "2025-01-01",
"assets": {
"binding": "ASSETS",
"directory": ".svelte-kit/cloudflare",
}
}
```
### Deployment
Follow [Cloudflare Workers framework guide](https://developers.cloudflare.com/workers/frameworks/framework-guides/svelte/).
## Cloudflare Pages
### Deployment
Follow [Get Started Guide](https://developers.cloudflare.com/pages/get-started/).
**Git integration build settings:**
- Framework preset: SvelteKit
- Build command: `npm run build` or `vite build`
- Build output directory: `.svelte-kit/cloudflare`
### Notes
Functions in `/functions` directory are ignored. Use SvelteKit [server endpoints](routing#server) instead (compiled to single `_worker.js`).
## Runtime APIs
Access `env` (bindings), `ctx`, `caches`, and `cf` via `platform` in hooks and endpoints:
```js
// @filename: ambient.d.ts
import { DurableObjectNamespace } from '@cloudflare/workers-types';
declare global {
namespace App {
interface Platform {
env: {
YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
};
}
}
}
// @filename: +server.js
// ---cut---
// @errors: 2355 2322
/// file: +server.js
/** @type {import('./$types').RequestHandler} */
export async function POST({ request, platform }) {
const x = platform?.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x');
}
```
> [!NOTE] Use SvelteKit's `$env` module for environment variables.
**Type definitions:**
```ts
/// file: src/app.d.ts
+++import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types';+++
declare global {
namespace App {
interface Platform {
+++ env: {
YOUR_KV_NAMESPACE: KVNamespace;
YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
};+++
}
}
}
export {};
```
### Testing Locally
`platform` values are emulated in dev/preview using Wrangler config bindings. Use `platformProxy` option to customize.
For build testing, use Wrangler 4:
- Workers: `wrangler dev .svelte-kit/cloudflare`
- Pages: `wrangler pages dev .svelte-kit/cloudflare`
## Headers and Redirects
`_headers` and `_redirects` files (in project root) only affect static assets. For dynamic responses, use server endpoints or `handle` hook.
## Troubleshooting
### Node.js Compatibility
```jsonc
/// file: wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat"]
}
```
### Worker Size Limits
If exceeding [size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size), import large libraries client-side only. See [FAQ](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window).
### Accessing File System
Can't use `fs`. Use [`read`]($app-server#read) from `$app/server` (fetches from deployed assets) or [prerender](page-options#prerender) routes.
## Migrating from Workers Sites
Replace `adapter-cloudflare-workers` with `adapter-cloudflare`. Update config:
```js
// @errors: 2307
/// file: svelte.config.js
---import adapter from '@sveltejs/adapter-cloudflare-workers';---
+++import adapter from '@sveltejs/adapter-cloudflare';+++
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;
```
```jsonc
/// file: wrangler.jsonc
{
--- "site": {
"bucket": ".cloudflare/public"
},---
+++ "assets": {
"directory": ".cloudflare/public",
"binding": "ASSETS" // Exclude this if you don't have a `main` key configured.
}+++
}
```

View File

@@ -0,0 +1,117 @@
# Netlify Adapter
Deploy to Netlify using [`adapter-netlify`](https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify). Auto-installed with `adapter-auto`.
## Usage
Install: `npm i -D @sveltejs/adapter-netlify`
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-netlify';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// default options are shown
adapter: adapter({
// if true, will create a Netlify Edge Function rather
// than using standard Node-based functions
edge: false,
// if true, will split your app into multiple functions
// instead of creating a single one for the entire app.
// if `edge` is true, this option cannot be used
split: false
})
}
};
export default config;
```
Requires `netlify.toml` in project root:
```toml
[build]
command = "npm run build"
publish = "build"
```
Default publish directory is `"build"` if not specified.
## Edge Functions
Set `edge: true` for Deno-based edge functions (deployed close to visitors). Default `false` uses Node-based functions.
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-netlify';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
// will create a Netlify Edge Function using Deno-based
// rather than using standard Node-based functions
edge: true
})
}
};
export default config;
```
## Netlify-Specific Features
### Headers & Redirects
Place `_headers` and `_redirects` files in project root for static assets.
**Gotchas:**
- `_redirects` has higher priority than `[[redirects]]` in `netlify.toml`
- Don't add catch-all rules like `/* /foobar/:splat` - they'll prevent auto-appended rules from working
- Only first matching rule is processed
### Forms
1. Create HTML form with hidden `form-name` input
2. Prerender the page (`export const prerender = true`) - Netlify's bot parses HTML at deploy time
3. Prerender custom success pages if specified
### Functions
Access Netlify context (including Identity info) via `event.platform.context`:
```js
// @errors: 2339
// @filename: ambient.d.ts
/// <reference types="@sveltejs/adapter-netlify" />
// @filename: +page.server.js
// ---cut---
/// file: +page.server.js
/** @type {import('./$types').PageServerLoad} */
export const load = async (event) => {
const context = event.platform?.context;
console.log(context); // shows up in your functions log in the Netlify app
};
```
Add custom functions via `netlify.toml`:
```toml
[build]
command = "npm run build"
publish = "build"
[functions]
directory = "functions"
```
## Troubleshooting
**File system access:**
- `fs` doesn't work in edge deployments
- In serverless, files aren't copied to deployment
- Use `read()` from `$app/server` instead (works in both edge and serverless)
- Or prerender the routes

View File

@@ -0,0 +1,229 @@
# adapter-node
Generates a standalone Node server.
## Usage
Install and configure:
```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-node';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;
```
## Deploying
Build with `npm run build`. Creates production server in output directory (default: `build`).
Need: output directory, `package.json`, and production `node_modules`. Generate production deps:
```sh
npm ci --omit dev
```
Start server:
```sh
node build
```
**Dev dependencies** are bundled via Rollup. **Dependencies** are externalized. Control this via `package.json` placement.
### Compressing responses
Use reverse proxy compression for better performance (Node is single-threaded). If using custom server middleware, use [`@polka/compression`](https://www.npmjs.com/package/@polka/compression) (supports streaming, unlike `compression` package).
## Environment variables
- **Dev/preview**: Reads `.env` files automatically
- **Production**: `.env` files NOT auto-loaded
Load in production:
```sh
# Using dotenv
node -r dotenv/config build
# Node.js v20.6+
node --env-file=.env build
```
### `PORT`, `HOST` and `SOCKET_PATH`
Default: `0.0.0.0:3000`
```sh
HOST=127.0.0.1 PORT=4000 node build
```
Use socket path (ignores HOST/PORT):
```sh
SOCKET_PATH=/tmp/socket node build
```
### `ORIGIN`, `PROTOCOL_HEADER`, `HOST_HEADER`, and `PORT_HEADER`
Set origin directly:
```sh
ORIGIN=https://my.site node build
```
Or use headers (only behind trusted reverse proxy):
```sh
PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build
```
Standard headers: `x-forwarded-proto`, `x-forwarded-host`, `x-forwarded-port` (set via `PORT_HEADER`).
**Incorrect URL causes error**: "Cross-site POST form submissions are forbidden"
### `ADDRESS_HEADER` and `XFF_DEPTH`
`event.getClientAddress()` returns connecting IP by default. Behind proxies, set:
```sh
ADDRESS_HEADER=True-Client-IP node build
```
For `X-Forwarded-For`, set `XFF_DEPTH` to number of trusted proxies (reads from right to avoid spoofing):
```sh
XFF_DEPTH=3
```
### `BODY_SIZE_LIMIT`
Max request body size. Default: `512kb`. Supports units: `K`, `M`, `G`. Disable with `Infinity`.
### `SHUTDOWN_TIMEOUT`
Seconds to wait before force-closing connections on `SIGTERM`/`SIGINT`. Default: `30`.
### `IDLE_TIMEOUT`
With systemd socket activation: seconds of inactivity before auto-sleep. Unset = runs continuously.
## Options
```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-node';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
// default options are shown
out: 'build',
precompress: true,
envPrefix: ''
})
}
};
export default config;
```
### out
Build directory. Default: `build`.
### precompress
Gzip/brotli precompression for assets and prerendered pages. Default: `true`.
### envPrefix
Prefix for environment variables:
```js
envPrefix: 'MY_CUSTOM_';
```
```sh
MY_CUSTOM_HOST=127.0.0.1 MY_CUSTOM_PORT=4000 node build
```
## Graceful shutdown
On `SIGTERM`/`SIGINT`:
1. Rejects new requests
2. Waits for pending requests to finish
3. Closes remaining connections after `SHUTDOWN_TIMEOUT`
Listen to shutdown event:
```js
// @errors: 2304
process.on('sveltekit:shutdown', async (reason) => {
await jobs.stop();
await db.close();
});
```
`reason` values: `SIGINT`, `SIGTERM`, `IDLE`
## Socket activation
Use systemd for on-demand scaling. Adapter listens on file descriptor 3 when `LISTEN_PID` and `LISTEN_FDS` are set.
1. Create service:
```ini
/// file: /etc/systemd/system/myapp.service
[Service]
Environment=NODE_ENV=production IDLE_TIMEOUT=60
ExecStart=/usr/bin/node /usr/bin/myapp/build
```
2. Create socket:
```ini
/// file: /etc/systemd/system/myapp.socket
[Socket]
ListenStream=3000
[Install]
WantedBy=sockets.target
```
3. Enable:
```sh
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.socket
```
## Custom server
Adapter creates `index.js` (standalone server) and `handler.js` (export for custom servers).
```js
// @errors: 2307 7006
/// file: my-server.js
import { handler } from './build/handler.js';
import express from 'express';
const app = express();
// add a route that lives separately from the SvelteKit app
app.get('/healthcheck', (req, res) => {
res.end('ok');
});
// let SvelteKit handle everything else, including serving prerendered pages and static assets
app.use(handler);
app.listen(3000, () => {
console.log('listening on port 3000');
});
```
Works with Express, Connect, Polka, or `http.createServer`.

View File

@@ -0,0 +1,150 @@
# adapter-static
Use SvelteKit as a static site generator (SSG) with [`adapter-static`](https://github.com/sveltejs/kit/tree/main/packages/adapter-static). Prerender entire site as static files.
## Usage
Install: `npm i -D @sveltejs/adapter-static`
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: false,
strict: true
})
}
};
export default config;
```
Enable prerendering in root layout:
```js
/// file: src/routes/+layout.js
export const prerender = true;
```
**Important:** Set [`trailingSlash`](page-options#trailingSlash) correctly. If host doesn't render `/a.html` for `/a` requests, use `trailingSlash: 'always'` to create `/a/index.html`.
## Zero-config support
Platforms like Vercel auto-configure. Omit options:
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;
```
## Options
- **pages**: Output directory for prerendered pages (default: `build`)
- **assets**: Output directory for static assets (default: same as `pages`)
- **fallback**: Filename for SPA entry point (e.g., `200.html`). Avoid `index.html`. Has negative performance/SEO impact. See [single page apps](single-page-apps) docs.
- **precompress**: If `true`, generates `.br` and `.gz` files
- **strict**: If `true` (default), checks all pages prerendered or `fallback` set. Set `false` to skip check.
## GitHub Pages
For repos not named `your-username.github.io`, set [`config.kit.paths.base`](configuration#paths) to repo name (site served from `https://your-username.github.io/your-repo-name`).
```js
// @errors: 2322
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
fallback: '404.html'
}),
paths: {
base: process.argv.includes('dev') ? '' : process.env.BASE_PATH
}
}
};
export default config;
```
Example GitHub Actions workflow:
```yaml
### file: .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: 'main'
jobs:
build_site:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# If you're using pnpm, add this step then change the commands and cache key below to use `pnpm`
# - name: Install pnpm
# uses: pnpm/action-setup@v3
# with:
# version: 8
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm i
- name: build
env:
BASE_PATH: '/${{ github.event.repository.name }}'
run: |
npm run build
- name: Upload Artifacts
uses: actions/upload-pages-artifact@v3
with:
# this should match the `pages` option in your adapter-static options
path: 'build/'
deploy:
needs: build_site
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
```
**Note:** If not using GitHub Actions, add empty `.nojekyll` file in `static` directory.

View File

@@ -0,0 +1,155 @@
# Vercel Adapter
Deploy to Vercel using [`adapter-vercel`](https://github.com/sveltejs/kit/tree/main/packages/adapter-vercel). Auto-installed with `adapter-auto`.
## Setup
```bash
npm i -D @sveltejs/adapter-vercel
```
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
// options here
})
}
};
export default config;
```
## Deployment Configuration
Configure via adapter options or `export const config` in `+server.js`, `+page(.server).js`, `+layout(.server).js`.
```js
/// file: about/+page.js
/** @type {import('@sveltejs/adapter-vercel').Config} */
export const config = {
split: true
};
```
### All Functions
- `runtime`: `'edge'`, `'nodejs18.x'`, `'nodejs20.x'`, `'nodejs22.x'` (deprecated, use Vercel dashboard config)
- `regions`: array of [edge regions](https://vercel.com/docs/concepts/edge-network/regions) or `'all'` for edge (default `["iad1"]` for serverless)
- `split`: deploy route as individual function
### Edge Functions Only
- `external`: dependencies to exclude from esbuild bundling
### Serverless Functions Only
- `memory`: 128-3008 Mb (default 1024)
- `maxDuration`: seconds (10 Hobby, 15 Pro, 900 Enterprise)
- `isr`: Incremental Static Regeneration config
Config in layouts applies to child routes unless overridden.
## Image Optimization
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
images: {
sizes: [640, 828, 1200, 1920, 3840],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 300,
domains: ['example-app.vercel.app'],
}
})
}
};
export default config;
```
## Incremental Static Regeneration (ISR)
[ISR](https://vercel.com/docs/incremental-static-regeneration) provides prerendered performance with dynamic flexibility.
**⚠️ Use only for content identical across all visitors. No user-specific data (sessions, cookies) or it will leak.**
```js
import { BYPASS_TOKEN } from '$env/static/private';
/** @type {import('@sveltejs/adapter-vercel').Config} */
export const config = {
isr: {
expiration: 60,
bypassToken: BYPASS_TOKEN,
allowQuery: ['search']
}
};
```
**Note:** ISR ignored on routes with `export const prerender = true`.
### Options
- `expiration` (required): seconds before re-generation (`false` = never)
- `bypassToken`: 32+ char token for cache bypass via `__prerender_bypass=<token>` cookie or `x-prerender-revalidate: <token>` header
- `allowQuery`: query params contributing to cache key (others ignored)
Generate token: `crypto.randomUUID()`. Set as `BYPASS_TOKEN` env var in Vercel. Pull locally: `vercel env pull .env.development.local`
## Environment Variables
Access [Vercel system env vars](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) from `$env/static/private` or `$env/dynamic/private`.
```js
/// file: +layout.server.js
import { VERCEL_COMMIT_REF } from '$env/static/private';
/** @type {import('./$types').LayoutServerLoad} */
export function load() {
return {
deploymentGitBranch: VERCEL_COMMIT_REF
};
}
```
```svelte
<!--- file: +layout.svelte --->
<script>
/** @type {import('./$types').LayoutProps} */
let { data } = $props();
</script>
<p>This staging environment was deployed from {data.deploymentGitBranch}.</p>
```
**Prefer `$env/static/private`** for static replacement and dead code elimination.
## Skew Protection
[Skew protection](https://vercel.com/docs/deployments/skew-protection) routes users to their original deployment via cookie. Enable in Vercel project settings > Advanced.
**Caveat:** Multiple tabs with different versions will route to newest, falling back to SvelteKit's built-in protection.
## Notes
### Vercel Functions
`/api/*` requests bypass SvelteKit if `api` directory exists at project root. Use [API routes](routing#server) instead.
### Node Version
Update in [project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version) if using old default.
## Troubleshooting
### File System Access
- **Edge functions:** `fs` unavailable
- **Serverless functions:** `fs` won't work as expected. Use [`read()`]($app-server#read) from `$app/server` instead
- Alternative: [prerender](page-options#prerender) routes
### Deployment Protection
If using [`read()`]($app-server#read) in edge functions with [Deployment Protection](https://vercel.com/docs/deployment-protection), enable [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation) to avoid 401 errors.

View File

@@ -0,0 +1,45 @@
# Adapters
Adapters are plugins that transform your built SvelteKit app for specific deployment targets.
## Official Adapters
- `@sveltejs/adapter-cloudflare` - Cloudflare Workers/Pages
- `@sveltejs/adapter-netlify` - Netlify
- `@sveltejs/adapter-node` - Node servers
- `@sveltejs/adapter-static` - Static site generation (SSG)
- `@sveltejs/adapter-vercel` - Vercel
Community adapters available for other platforms.
## Configuration
Specify adapter in `svelte.config.js`:
```js
/// file: svelte.config.js
// @filename: ambient.d.ts
declare module 'svelte-adapter-foo' {
const adapter: (opts: any) => import('@sveltejs/kit').Adapter;
export default adapter;
}
// @filename: index.js
// ---cut---
import adapter from 'svelte-adapter-foo';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
// adapter options go here
})
}
};
export default config;
```
## Platform-Specific Context
Some adapters provide additional request info via the `platform` property in `RequestEvent` (available in hooks and server routes). Example: Cloudflare Workers expose `env` object with KV namespaces. Check adapter docs for details.

View File

@@ -0,0 +1,19 @@
# Resources
## FAQs
- [SvelteKit FAQ](faq)
- [Svelte FAQ](../svelte/faq)
- [`vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md)
## Examples
Official:
- [`sveltejs/realworld`](https://github.com/sveltejs/realworld) - blog site
- [HackerNews clone](https://github.com/sveltejs/sites/tree/master/sites/hn.svelte.dev)
- [`svelte.dev`](https://github.com/sveltejs/svelte.dev)
Community: [#sveltekit](https://github.com/topics/sveltekit), [#sveltekit-template](https://github.com/topics/sveltekit-template), [Svelte Society](https://sveltesociety.dev/templates?category=sveltekit)
## Support
[Discord](/chat) | [StackOverflow](https://stackoverflow.com/questions/tagged/sveltekit)
Search FAQ/docs/issues first before asking.

View File

@@ -0,0 +1,206 @@
# Advanced Routing
## Rest Parameters
Use `[...param]` for unknown number of segments:
```sh
/[org]/[repo]/tree/[branch]/[...file]
```
Request `/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md` produces:
```js
// @noErrors
{
org: 'sveltejs',
repo: 'kit',
branch: 'main',
file: 'documentation/docs/04-advanced-routing.md'
}
```
**Note:** `src/routes/a/[...rest]/z/+page.svelte` matches `/a/z` (no parameter), `/a/b/z`, `/a/b/c/z`, etc. Validate rest parameters using matchers.
### 404 Pages
Create catch-all route for custom 404s:
```tree
src/routes/
├ marx-brothers/
│ ├ [...path]/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
```
```js
/// file: src/routes/marx-brothers/[...path]/+page.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function load(event) {
error(404, 'Not Found');
}
```
**Note:** Unhandled 404s appear in `handleError`.
## Optional Parameters
Wrap parameter in double brackets: `[[lang]]/home` matches both `/home` and `/en/home`.
**Note:** Optional parameters cannot follow rest parameters (`[...rest]/[[optional]]`).
## Matching
Validate parameters with matchers in `src/params/`:
```js
/// file: src/params/fruit.js
/**
* @param {string} param
* @return {param is ('apple' | 'orange')}
* @satisfies {import('@sveltejs/kit').ParamMatcher}
*/
export function match(param) {
return param === 'apple' || param === 'orange';
}
```
Use in routes: `src/routes/fruits/[page=fruit]`
**Note:** Matchers run on server and browser. `*.test.js` and `*.spec.js` files are ignored.
## Sorting
Multiple routes can match same path. Priority rules:
1. More specific routes rank higher
2. Parameters with matchers (`[name=type]`) beat those without (`[name]`)
3. `[[optional]]` and `[...rest]` have lowest priority (ignored unless final segment)
4. Ties resolved alphabetically
Example ordering:
```sh
src/routes/foo-abc/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/[[a=x]]/+page.svelte
src/routes/[b]/+page.svelte
src/routes/[...catchall]/+page.svelte
```
## Encoding
Use hexadecimal escapes `[x+nn]` for special characters:
- `\``[x+5c]`, `/``[x+2f]`, `:``[x+3a]`, `*``[x+2a]`
- `?``[x+3f]`, `"``[x+22]`, `<``[x+3c]`, `>``[x+3e]`
- `|``[x+7c]`, `#``[x+23]`, `%``[x+25]`
- `[``[x+5b]`, `]``[x+5d]`, `(``[x+28]`, `)``[x+29]`
Example: `/smileys/:-)``src/routes/smileys/[x+3a]-[x+29]/+page.svelte`
Unicode escapes: `[u+nnnn]` (e.g., `[u+d83e][u+dd2a]` or `🤪`)
**Tip:** Encode leading `.` for TypeScript compatibility: `[x+2e]well-known`
## Advanced Layouts
### (group)
Parentheses create route groups without affecting URLs:
```tree
src/routes/
│ (app)/
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
│ (marketing)/
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte
```
`/admin` doesn't inherit `(app)` or `(marketing)` layouts.
### +page@
Break out of layouts with `@segment`:
```tree
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page@(app).svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
```
Options:
- `+page@[id].svelte` — inherits from `[id]/+layout.svelte`
- `+page@item.svelte` — inherits from `item/+layout.svelte`
- `+page@(app).svelte` — inherits from `(app)/+layout.svelte`
- `+page@.svelte` — inherits from root layout
### +layout@
Layouts can also break out:
```
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte // uses (app)/item/[id]/+layout.svelte
│ │ │ ├ +layout.svelte // inherits from (app)/item/+layout@.svelte
│ │ │ └ +page.svelte // uses (app)/item/+layout@.svelte
│ │ └ +layout@.svelte // inherits from root layout, skipping (app)/+layout.svelte
│ └ +layout.svelte
└ +layout.svelte
```
### When to Use Groups
Groups aren't always necessary. Alternative: reuse components/functions:
```svelte
<!--- file: src/routes/nested/route/+layout@.svelte --->
<script>
import ReusableLayout from '$lib/ReusableLayout.svelte';
let { data, children } = $props();
</script>
<ReusableLayout {data}>
{@render children()}
</ReusableLayout>
```
```js
/// file: src/routes/nested/route/+layout.js
// @filename: ambient.d.ts
declare module "$lib/reusable-load-function" {
export function reusableLoad(event: import('@sveltejs/kit').LoadEvent): Promise<Record<string, any>>;
}
// @filename: index.js
// ---cut---
import { reusableLoad } from '$lib/reusable-load-function';
/** @type {import('./$types').PageLoad} */
export function load(event) {
// Add additional logic here, if needed
return reusableLoad(event);
}
```

View File

@@ -0,0 +1,30 @@
# Auth
Authentication verifies user identity via credentials. Authorization determines allowed actions.
## Sessions vs Tokens
**Sessions:**
- Stored in database
- Can be immediately revoked
- Require DB query per request
**JWT:**
- Not checked against datastore
- Cannot be immediately revoked
- Better latency, reduced DB load
## Integration
- Check auth [cookies](@sveltejs-kit#Cookies) in [server hooks](hooks#Server-hooks)
- Store user info in [`locals`](hooks#Server-hooks-locals)
## Implementation
[Lucia](https://lucia-auth.com/) provides session-based auth examples for SvelteKit.
Add via:
- `npx sv create` (new project)
- `npx sv add lucia` (existing project)
SvelteKit-specific guides preferred over generic JS auth libraries to avoid multiple framework dependencies.

View File

@@ -0,0 +1,26 @@
# Building
Build happens in two stages via `vite build`:
1. Vite creates optimized production build (server, browser, service worker). Prerendering runs here.
2. Adapter tunes build for target environment.
## During build
Code in `+page/layout(.server).js` files executes during build analysis. Prevent execution with `building` check:
```js
import { building } from '$app/environment';
import { setupMyDatabase } from '$lib/server/database';
if (!building) {
setupMyDatabase();
}
export function load() {
// ...
}
```
## Preview
`vite preview` runs production build locally in Node. Not perfect reproduction - adapter-specific features like `platform` object don't apply.

View File

@@ -0,0 +1,11 @@
# SvelteKit CLI
SvelteKit uses [Vite](https://vitejs.dev) CLI via npm scripts:
- `vite dev` — dev server
- `vite build` — production build
- `vite preview` — preview production build
## svelte-kit sync
Creates `tsconfig.json` and generated types (importable as `./$types` in routing files). Runs automatically as `prepare` script in npm lifecycle.

View File

@@ -0,0 +1,269 @@
# svelte.config.js
Configuration file at project root. Used by SvelteKit and other Svelte tooling.
```js
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;
```
## adapter
**Default:** `undefined`
Runs during `vite build`. Determines how output is converted for different platforms.
## alias
**Default:** `{}`
Replace values in `import` statements. Automatically passed to Vite and TypeScript.
```js
/// file: svelte.config.js
const config = {
kit: {
alias: {
'my-file': 'path/to/my-file.js',
'my-directory': 'path/to/my-directory',
'my-directory/*': 'path/to/my-directory/*'
}
}
};
```
> Built-in `$lib` alias controlled by `config.kit.files.lib`. Run `npm run dev` to generate alias config in `jsconfig.json`/`tsconfig.json`.
## appDir
**Default:** `"_app"`
Directory for SvelteKit assets (JS, CSS) and internal routes.
## csp
Content Security Policy configuration. Protects against XSS attacks.
```js
/// file: svelte.config.js
const config = {
kit: {
csp: {
directives: {
'script-src': ['self']
},
reportOnly: {
'script-src': ['self'],
'report-uri': ['/']
}
}
}
};
```
SvelteKit augments directives with nonces/hashes for inline styles/scripts. Use `%sveltekit.nonce%` placeholder in `src/app.html`.
For prerendered pages, CSP added via `<meta http-equiv>` tag (`frame-ancestors`, `report-uri`, `sandbox` ignored).
**mode:** `'hash' | 'nonce' | 'auto'` - `'auto'` uses hashes for prerendered, nonces for dynamic pages.
> Most Svelte transitions create inline `<style>` elements. Leave `style-src` unspecified or add `unsafe-inline`.
## csrf
**Default:** `{ checkOrigin: true, trustedOrigins: [] }`
Protection against cross-site request forgery.
- **checkOrigin** (deprecated, use `trustedOrigins: ['*']`): Verifies `origin` header for POST/PUT/PATCH/DELETE form submissions matches server origin.
- **trustedOrigins:** Array of origins allowed for cross-origin form submissions (e.g., `['https://payment-gateway.com']`). Use `['*']` to trust all (not recommended).
> CSRF checks only apply in production.
## embedded
**Default:** `false`
If `true`, SvelteKit adds event listeners on parent of `%sveltekit.body%` instead of `window`, and uses server `params` instead of inferring from `location.pathname`.
## env
Environment variable configuration.
- **dir** (default: `"."`): Directory to search for `.env` files.
- **publicPrefix** (default: `"PUBLIC_"`): Prefix for client-safe env vars. See `$env/static/public` and `$env/dynamic/public`.
- **privatePrefix** (default: `""`, since v1.21.0): Prefix for server-only env vars. Vars matching neither prefix are discarded. See `$env/static/private` and `$env/dynamic/private`.
## experimental
Experimental features (not subject to semver).
- **tracing.server** (default: `false`, since v2.31.0): Enable OpenTelemetry tracing for `handle` hook, `load` functions, form actions, remote functions.
- **instrumentation.server** (default: `false`, since v2.31.0): Enable `instrumentation.server.js` for tracing/observability.
- **remoteFunctions** (default: `false`): Enable experimental remote functions feature.
## files (deprecated)
- **src** (default: `"src"`, since v2.28): Source code location.
- **assets** (default: `"static"`): Static files with stable URLs.
- **hooks.client** (default: `"src/hooks.client"`): Client hooks location.
- **hooks.server** (default: `"src/hooks.server"`): Server hooks location.
- **hooks.universal** (default: `"src/hooks"`, since v2.3.0): Universal hooks location.
- **lib** (default: `"src/lib"`): Internal library, accessible as `$lib`.
- **params** (default: `"src/params"`): Parameter matchers directory.
- **routes** (default: `"src/routes"`): App structure/routing files.
- **serviceWorker** (default: `"src/service-worker"`): Service worker entry point.
- **appTemplate** (default: `"src/app.html"`): HTML response template.
- **errorTemplate** (default: `"src/error.html"`): Fallback error response template.
## inlineStyleThreshold
**Default:** `0`
Inline CSS in `<style>` block if file length (UTF-16 code units) is below threshold. Reduces initial requests but increases HTML size and reduces browser cache effectiveness.
## moduleExtensions
**Default:** `[".js", ".ts"]`
File extensions SvelteKit treats as modules. Files not matching `config.extensions` or `config.kit.moduleExtensions` are ignored by router.
## outDir
**Default:** `".svelte-kit"`
Directory for SvelteKit build files. Exclude from version control.
## output
### preloadStrategy
**Default:** `"modulepreload"` (since v1.8.4)
Preload strategy for initial page JS modules:
- `'modulepreload'`: Uses `<link rel="modulepreload">`. Best for Chromium, Firefox 115+, Safari 17+.
- `'preload-js'`: Uses `<link rel="preload">`. Prevents waterfalls but Chromium parses modules twice. Modules requested twice in Firefox.
- `'preload-mjs'`: Uses `<link rel="preload">` with `.mjs` extension. Prevents double-parsing in Chromium but some servers may not serve `.mjs` correctly.
### bundleStrategy
**Default:** `'split'` (since v2.13.0)
- `'split'`: Multiple .js/.css files loaded lazily (recommended).
- `'single'`: One .js bundle and one .css file for entire app.
- `'inline'`: Inlines all JS/CSS into HTML (usable without server).
For `'split'`, adjust bundling via Vite's `build.rollupOptions` (`output.experimentalMinChunkSize`, `output.manualChunks`).
For inline assets, set Vite's `build.assetsInlineLimit`:
```js
/// file: vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
build: {
assetsInlineLimit: Infinity
}
});
```
```svelte
/// file: src/routes/+layout.svelte
<script>
import favicon from './favicon.png';
</script>
<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>
```
## paths
- **assets** (default: `""`): Absolute path for serving app files (e.g., storage bucket URL).
- **base** (default: `""`): Root-relative path starting with `/` (e.g., `/base-path`). Prepend to links: `<a href="{base}/your-page">`. Import from `$app/paths`.
- **relative** (default: `true`, since v1.9.0): Use relative asset paths. If `true`, `base` and `assets` from `$app/paths` replaced with relative paths during SSR. If `false`, always root-relative. Set to `false` if using `<base>` element. SPA fallback pages always use absolute paths.
## prerender
- **concurrency** (default: `1`): Max simultaneous prerendered pages.
- **crawl** (default: `true`): Follow links from `entries` to find pages.
- **entries** (default: `["*"]`): Pages to prerender or start crawling from. `*` includes all routes with no required `[parameters]`.
- **handleHttpError** (default: `"fail"`, since v1.15.7): `'fail' | 'ignore' | 'warn' | (details) => void`. Custom handler receives `status`, `path`, `referrer`, `referenceType`, `message`.
```js
/// file: svelte.config.js
const config = {
kit: {
prerender: {
handleHttpError: ({ path, referrer, message }) => {
if (path === '/not-found' && referrer === '/blog/how-we-built-our-404-page') {
return;
}
throw new Error(message);
}
}
}
};
```
- **handleMissingId** (default: `"fail"`, since v1.15.7): `'fail' | 'ignore' | 'warn' | (details) => void`. Handler for hash links without corresponding `id`. Receives `path`, `id`, `referrers`, `message`.
- **handleEntryGeneratorMismatch** (default: `"fail"`, since v1.16.0): `'fail' | 'ignore' | 'warn' | (details) => void`. Handler for entry/route mismatch. Receives `generatedFromId`, `entry`, `matchedId`, `message`.
- **handleUnseenRoutes** (default: `"fail"`, since v2.16.0): `'fail' | 'ignore' | 'warn' | (details) => void`. Handler for prerenderable routes not prerendered. Receives `routes`.
- **origin** (default: `"http://sveltekit-prerender"`): Value of `url.origin` during prerendering.
## router
- **type** (default: `"pathname"`, since v2.14.0): `'pathname' | 'hash'`. `'pathname'` uses URL pathname. `'hash'` uses `location.hash` (disables SSR/prerendering, links must start with `#/`).
- **resolution** (default: `"client"`, since v2.17.0): `'client' | 'server'`. `'client'` uses route manifest in browser. `'server'` determines route on server for each unvisited path (faster initial load, hidden routes, server interception, but slightly slower for unvisited paths).
## version
Client-side navigation version management. Detects new deployments and falls back to full-page navigation on errors.
```html
/// file: +layout.svelte
<script>
import { beforeNavigate } from '$app/navigation';
import { updated } from '$app/state';
beforeNavigate(({ willUnload, to }) => {
if (updated.current && !willUnload && to?.url) {
location.href = to.url.href;
}
});
</script>
```
- **name:** App version string (must be deterministic, e.g., commit hash). Defaults to build timestamp.
```js
/// file: svelte.config.js
import * as child_process from 'node:child_process';
export default {
kit: {
version: {
name: child_process.execSync('git rev-parse HEAD').toString().trim()
}
}
};
```
- **pollInterval** (default: `0`): Milliseconds to poll for version changes. Sets `updated.current` to `true` when new version detected.
## typescript
- **config** (default: `(config) => config`, since v1.3.0): Function to edit generated `tsconfig.json`. Mutate or return new config. Paths relative to `.svelte-kit/tsconfig.json`.

View File

@@ -0,0 +1,22 @@
# Getting Started
## Quick Start
```sh
npx sv create my-app
cd my-app
npm run dev
```
- `npx sv create` scaffolds project with optional TypeScript setup
- `npm run dev` starts dev server at [localhost:5173](http://localhost:5173)
## Core Concepts
- **Pages** = Svelte components
- **Routing** = Files in `src/routes/` directory
- **Rendering** = Server-rendered first visit, then client-side app
## Editor Setup
Recommended: [VS Code](https://code.visualstudio.com/download) + [Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode)

View File

@@ -0,0 +1,56 @@
# Debugging
Debug Svelte/SvelteKit projects using breakpoints in frontend and backend code. Assumes Node.js runtime.
## Visual Studio Code
### Built-in Debug Terminal
1. Open command palette: `CMD/Ctrl` + `Shift` + `P`
2. Launch "Debug: JavaScript Debug Terminal"
3. Start project: `npm run dev`
4. Set breakpoints in source files
5. Trigger breakpoint
### Launch via Debug Pane
Create `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"command": "npm run dev",
"name": "Run development server",
"request": "launch",
"type": "node-terminal"
}
]
}
```
Or auto-generate: Go to "Run and Debug" pane → Select "Node.js..." → Choose run script → Press `F5`
Docs: <https://code.visualstudio.com/docs/editor/debugging>
## Other Editors
- [WebStorm Svelte: Debug Your Application](https://www.jetbrains.com/help/webstorm/svelte.html#ws_svelte_debug)
- [Debugging JavaScript Frameworks in Neovim](https://theosteiner.de/debugging-javascript-frameworks-in-neovim)
## Chrome/Edge DevTools
**Note:** Only works with client-side SvelteKit source maps.
1. Start with `--inspect` flag: `NODE_OPTIONS="--inspect" npm run dev`
2. Open site (typically `localhost:5173`)
3. Open dev tools → Click "Open dedicated DevTools for Node.js" icon (Node.js logo)
4. Set breakpoints
Alternative: Navigate to `chrome://inspect` (Chrome) or `edge://inspect` (Edge)
## References
- [Debugging Node.js](https://nodejs.org/en/learn/getting-started/debugging)

View File

@@ -0,0 +1,105 @@
# Errors
## Error Objects
SvelteKit distinguishes between **expected** and **unexpected** errors. Both are `{ message: string }` objects by default.
## Expected Errors
Created with `error()` helper from `@sveltejs/kit`:
```js
/// file: src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
const post = await db.getPost(params.slug);
if (!post) {
error(404, {
message: 'Not found'
});
}
return { post };
}
```
Sets response status and renders nearest `+error.svelte` component where `page.error` is the error object.
```svelte
<!--- file: src/routes/+error.svelte --->
<script>
import { page } from '$app/state';
</script>
<h1>{page.error.message}</h1>
```
Add extra properties:
```js
error(404, {
message: 'Not found',
code: 'NOT_FOUND'
});
```
Or use string shorthand:
```js
error(404, 'Not found');
```
## Unexpected Errors
Any other exception during request handling. Not exposed to users (default: `{ "message": "Internal Error" }`).
Logged to console/server logs and passed through `handleError` hook for custom handling.
## Responses
**In `handle` or `+server.js`:** Returns fallback error page or JSON based on `Accept` headers.
**In `load` functions:** Renders nearest `+error.svelte` component. If error occurs in `+layout(.server).js`, uses `+error.svelte` _above_ that layout.
**Root layout errors:** Use fallback error page since root layout contains `+error.svelte`.
### Custom Fallback Error Page
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>%sveltekit.error.message%</title>
</head>
<body>
<h1>My custom error page</h1>
<p>Status: %sveltekit.status%</p>
<p>Message: %sveltekit.error.message%</p>
</body>
</html>
```
## Type Safety
Customize error shape via `App.Error` interface:
```ts
/// file: src/app.d.ts
declare global {
namespace App {
interface Error {
code: string;
id: string;
}
}
}
export {};
```
Always includes `message: string` property.

View File

@@ -0,0 +1,168 @@
# SvelteKit FAQ
## Including package.json data
Load JSON with import attributes:
```ts
/// file: svelte.config.js
import pkg from './package.json' with { type: 'json' };
```
## Package inclusion issues
Check packaging compatibility at [publint.dev](https://publint.dev/).
**Key packaging requirements:**
- `exports` takes precedence over `main`/`module` (prevents deep imports)
- ESM files: `.mjs` extension OR `"type": "module"` in package.json (then CJS uses `.cjs`)
- `main` should be defined if no `exports`
- Svelte components: distribute as uncompiled `.svelte` files with ESM JS only
- Preprocess TypeScript/SCSS to vanilla JS/CSS (use [`svelte-package`](./packaging))
ESM versions work best with Vite. CJS dependencies are auto-converted by `vite-plugin-svelte` using `esbuild`.
If issues persist, check [Vite issues](https://github.com/vitejs/vite/issues) or try adjusting [`optimizeDeps`](https://vitejs.dev/config/#dep-optimization-options)/[`ssr`](https://vitejs.dev/config/#ssr-options) config (temporary workaround).
## View transitions API
Use `document.startViewTransition` in `onNavigate`:
```js
import { onNavigate } from '$app/navigation';
onNavigate((navigation) => {
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});
```
## Database setup
- Query databases in [server routes](./routing#server), not `.svelte` files
- Create `db.js` singleton for connection
- Run setup code in `hooks.server.js`
- Use [Svelte CLI](/docs/cli/overview) for automatic setup
## Client-side only code
**Browser check:**
```js
import { browser } from '$app/environment';
if (browser) {
// client-only code
}
```
**onMount (runs after first render):**
```js
import { onMount } from 'svelte';
onMount(async () => {
const { method } = await import('some-browser-only-library');
method('hello world');
});
```
**Static import (tree-shaken on server):**
```js
import { onMount } from 'svelte';
import { method } from 'some-browser-only-library';
onMount(() => {
method('hello world');
});
```
**Await block:**
```svelte
<script>
import { browser } from '$app/environment';
const ComponentConstructor = browser ?
import('some-browser-only-library').then((module) => module.Component) :
new Promise(() => {});
</script>
{#await ComponentConstructor}
<p>Loading...</p>
{:then component}
<svelte:component this={component} />
{:catch error}
<p>Something went wrong: {error.message}</p>
{/await}
```
## External API server
**Options:**
1. Use [`event.fetch`](./load#Making-fetch-requests) (requires handling CORS)
2. Set up proxy: production rewrites or Vite's [`server.proxy`](https://vitejs.dev/config/server-options.html#server-proxy)
3. Create API route proxy:
```js
/// file: src/routes/api/[...path]/+server.js
/** @type {import('./$types').RequestHandler} */
export function GET({ params, url }) {
return fetch(`https://example.com/${params.path + url.search}`);
}
```
See [`handleFetch`](./hooks#Server-hooks-handleFetch) for fetch customization.
## Middleware
**Production:** `adapter-node` builds middleware for custom servers
**Dev:** Use Vite plugin:
```js
import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').Plugin} */
const myPlugin = {
name: 'log-request-middleware',
configureServer(server) {
server.middlewares.use((req, res, next) => {
console.log(`Got request ${req.url}`);
next();
});
}
};
/** @type {import('vite').UserConfig} */
const config = {
plugins: [myPlugin, sveltekit()]
};
export default config;
```
See [Vite's `configureServer` docs](https://vitejs.dev/guide/api-plugin.html#configureserver).
## Yarn compatibility
**Yarn 2:** PnP is broken with ESM. Use `nodeLinker: 'node-modules'` in `.yarnrc.yml` or switch to npm/pnpm.
**Yarn 3:** ESM support is experimental. Setup:
```sh
yarn create svelte myapp
cd myapp
yarn set version berry
yarn install
```
Add to `.yarnrc.yml`:
```yaml
nodeLinker: node-modules
```
(Avoids build failures with `enableGlobalCache`)

View File

@@ -0,0 +1,419 @@
# Form Actions
`+page.server.js` files export actions to `POST` data to the server using `<form>` elements. Works without JavaScript, but can be progressively enhanced.
## Default Actions
```js
/// file: src/routes/login/+page.server.js
/** @satisfies {import('./$types').Actions} */
export const actions = {
default: async (event) => {
// TODO log the user in
}
};
```
```svelte
<!--- file: src/routes/login/+page.svelte --->
<form method="POST">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
</form>
```
Invoke from other pages with `action` attribute:
```html
<form method="POST" action="/login">
```
> Actions always use `POST` requests.
## Named Actions
```js
/// file: src/routes/login/+page.server.js
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async (event) => {
// TODO log the user in
},
register: async (event) => {
// TODO register the user
}
};
```
Invoke with query parameter prefixed by `/`:
```svelte
<form method="POST" action="?/register">
```
```svelte
<form method="POST" action="/login?/register">
```
Use `formaction` on buttons:
```svelte
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
```
> Can't have default actions next to named actions - query parameter persists in URL.
## Action Anatomy
Actions receive `RequestEvent`, read data with `request.formData()`. Return data available via `form` prop and `page.form`.
```js
/// file: src/routes/login/+page.server.js
import * as db from '$lib/server/db';
/** @type {import('./$types').PageServerLoad} */
export async function load({ cookies }) {
const user = await db.getUserFromSession(cookies.get('sessionid'));
return { user };
}
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
cookies.set('sessionid', await db.createSession(user), { path: '/' });
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
```
```svelte
<!--- file: src/routes/login/+page.svelte --->
<script>
/** @type {import('./$types').PageProps} */
let { data, form } = $props();
</script>
{#if form?.success}
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
```
### Validation Errors
Use `fail()` to return HTTP status code (400/422) with validation errors:
```js
/// file: src/routes/login/+page.server.js
import { fail } from '@sveltejs/kit';
import * as db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
if (!email) {
return fail(400, { email, missing: true });
}
const user = await db.getUser(email);
if (!user || user.password !== db.hash(password)) {
return fail(400, { email, incorrect: true });
}
cookies.set('sessionid', await db.createSession(user), { path: '/' });
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
```
```svelte
/// file: src/routes/login/+page.svelte
<form method="POST" action="?/login">
{#if form?.missing}<p class="error">The email field is required</p>{/if}
{#if form?.incorrect}<p class="error">Invalid credentials!</p>{/if}
<label>
Email
<input name="email" type="email" value={form?.email ?? ''}>
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
```
### Redirects
```js
/// file: src/routes/login/+page.server.js
import { fail, redirect } from '@sveltejs/kit';
import * as db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request, url }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
if (!user) {
return fail(400, { email, missing: true });
}
if (user.password !== db.hash(password)) {
return fail(400, { email, incorrect: true });
}
cookies.set('sessionid', await db.createSession(user), { path: '/' });
if (url.searchParams.has('redirectTo')) {
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
```
## Loading Data
Page re-renders after action runs. `load` functions run after action completes.
`handle` runs before action and doesn't rerun before `load`. Update `event.locals` in actions if needed:
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
event.locals.user = await getUser(event.cookies.get('sessionid'));
return resolve(event);
}
```
```js
/// file: src/routes/account/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export function load(event) {
return {
user: event.locals.user
};
}
/** @satisfies {import('./$types').Actions} */
export const actions = {
logout: async (event) => {
event.cookies.delete('sessionid', { path: '/' });
event.locals.user = null;
}
};
```
## Progressive Enhancement
### use:enhance
```svelte
/// file: src/routes/login/+page.svelte
<script>
import { enhance } from '$app/forms';
/** @type {import('./$types').PageProps} */
let { form } = $props();
</script>
<form method="POST" use:enhance>
```
> Only works with `method="POST"` pointing to `+page.server.js` actions.
Without arguments, `use:enhance`:
- Updates `form`, `page.form`, `page.status` on success/invalid response (only if action is on same page)
- Resets `<form>` element
- Invalidates all data with `invalidateAll` on success
- Calls `goto` on redirect
- Renders nearest `+error` boundary on error
- Resets focus
### Customising use:enhance
```svelte
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
// `formElement` is this `<form>` element
// `formData` is its `FormData` object that's about to be submitted
// `action` is the URL to which the form is posted
// calling `cancel()` will prevent the submission
// `submitter` is the `HTMLElement` that caused the form to be submitted
return async ({ result, update }) => {
// `result` is an `ActionResult` object
// `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
};
}}
>
```
Use `applyAction` to apply default behavior:
```svelte
/// file: src/routes/login/+page.svelte
<script>
import { enhance, applyAction } from '$app/forms';
/** @type {import('./$types').PageProps} */
let { form } = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>
```
`applyAction(result)` behavior:
- `success`, `failure` — sets `page.status`, updates `form` and `page.form` (regardless of submission location)
- `redirect` — calls `goto(result.location, { invalidateAll: true })`
- `error` — renders nearest `+error` boundary
### Custom Event Listener
```svelte
<!--- file: src/routes/login/+page.svelte --->
<script>
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
/** @type {import('./$types').PageProps} */
let { form } = $props();
/** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
event.preventDefault();
const data = new FormData(event.currentTarget, event.submitter);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
/** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit={handleSubmit}>
<!-- content -->
</form>
```
Use `deserialize()` not `JSON.parse()` - supports `Date` and `BigInt`.
If `+server.js` exists alongside `+page.server.js`, add header to POST to action:
```js
const response = await fetch(this.action, {
method: 'POST',
body: data,
headers: {
'x-sveltekit-action': 'true'
}
});
```
## Alternatives
Use `+server.js` for JSON APIs:
```svelte
<!--- file: src/routes/send-message/+page.svelte --->
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Rerun CI</button>
```
```js
/// file: src/routes/api/ci/+server.js
/** @type {import('./$types').RequestHandler} */
export function POST() {
// do something
}
```
## GET vs POST
Use `method="GET"` for forms that don't POST data (e.g., search). SvelteKit treats them like `<a>` elements:
```html
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>
```
Navigates to `/search?q=...`, invokes `load` but not actions. Supports `data-sveltekit-*` attributes like links.

View File

@@ -0,0 +1,49 @@
# SvelteKit Rendering
## Key Terms
### CSR (Client-Side Rendering)
Page generation in the browser using JavaScript. Enabled by default.
- Disable with `csr = false` page option
### SSR (Server-Side Rendering)
Page generation on the server. Enabled by default. Improves performance and SEO.
- Disable with `ssr = false` page option
### Hybrid App (Default)
SvelteKit's default mode: initial HTML from server (SSR), then subsequent navigations via CSR.
### Hydration
Process where client-side components initialize with server-sent data, attach event listeners, and become interactive. Data fetched during SSR is transmitted to client to avoid duplicate API calls.
- Disabled when `csr = false`
### Prerendering
Computing page contents at build time, saving HTML. Same content served to all users.
- **Rule**: Any two users must get identical content; page cannot contain actions
- Can include personalized content if fetched client-side
- Control with `prerender` page option or config in `svelte.config.js`
### SSG (Static Site Generation)
Every page is prerendered. No SSR servers needed, served from CDNs.
- Use `adapter-static` or set all pages to prerender
### ISR (Incremental Static Regeneration)
Generate static pages on-demand as visitors request them. Reduces build times vs full SSG.
- Available with `adapter-vercel`
### Routing (Client-Side)
SvelteKit intercepts navigation and updates page on client instead of server requests. Enabled by default.
- Skip with `data-sveltekit-reload` attribute
### SPA (Single-Page App)
Empty HTML shell on initial request, all rendering client-side. **Not recommended** due to performance/SEO impacts.
- Build with `adapter-static`
### Edge
Rendering in CDN near user for lower latency.
### MPA (Multi-Page App)
Traditional server-rendered apps (non-JavaScript).
### PWA (Progressive Web App)
Web app functioning like native app. Can be installed, use service workers for offline capability.

View File

@@ -0,0 +1,255 @@
# Hooks
App-wide functions that SvelteKit calls in response to specific events.
Three optional hook files:
- `src/hooks.server.js` — server hooks
- `src/hooks.client.js` — client hooks
- `src/hooks.js` — universal hooks (run on both)
Code runs at app startup, useful for initializing database clients.
## Server hooks
### handle
Runs on every server request (including prerendering). Receives `event` and `resolve` function.
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
if (event.url.pathname.startsWith('/custom')) {
return new Response('custom response');
}
const response = await resolve(event);
return response;
}
```
Default: `({ event, resolve }) => resolve(event)`
**locals**: Add custom data to `event.locals` for use in handlers and `load` functions:
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.cookies.get('sessionid'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
return response;
}
```
**resolve options** (second parameter):
- `transformPageChunk({ html, done })` — transform HTML chunks
- `filterSerializedResponseHeaders(name, value)` — filter headers in serialized responses (default: none)
- `preload({ type, path })` — determine which files to preload (default: `js` and `css`)
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('old', 'new'),
filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
preload: ({ type, path }) => type === 'js' || path.includes('/important/')
});
return response;
}
```
### handleFetch
Modify `event.fetch` calls on server/during prerendering:
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
```
For sibling subdomains, manually include cookies:
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
if (request.url.startsWith('https://api.my-domain.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
}
return fetch(request);
}
```
### handleValidationError
Called when remote function argument doesn't match schema. Returns `App.Error` shape:
```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function handleValidationError({ issues }) {
return {
message: 'No thank you'
};
}
```
## Shared hooks
Available in both `src/hooks.server.js` and `src/hooks.client.js`:
### handleError
Called for unexpected errors. Log errors and return safe error representation:
```js
/// file: src/hooks.server.js
import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: 'Whoops!',
errorId
};
}
```
```js
/// file: src/hooks.client.js
import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit').HandleClientError} */
export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: 'Whoops!',
errorId
};
}
```
Customize error shape via `App.Error` interface:
```ts
/// file: src/app.d.ts
declare global {
namespace App {
interface Error {
message: string;
errorId: string;
}
}
}
export {};
```
> **Note**: `handleError` must never throw. Not called for expected errors (using `error()` from `@sveltejs/kit`).
### init
Runs once at server creation or app start. For async initialization:
```js
/// file: src/hooks.server.js
import * as db from '$lib/server/database';
/** @type {import('@sveltejs/kit').ServerInit} */
export async function init() {
await db.connect();
}
```
## Universal hooks
In `src/hooks.js`, runs on both server and client:
### reroute
Changes URL-to-route translation. Runs before `handle`:
```js
/// file: src/hooks.js
/** @type {Record<string, string>} */
const translated = {
'/en/about': '/en/about',
'/de/ueber-uns': '/de/about',
'/fr/a-propos': '/fr/about',
};
/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
if (url.pathname in translated) {
return translated[url.pathname];
}
}
```
Can be async (since v2.18):
```js
/// file: src/hooks.js
/** @type {import('@sveltejs/kit').Reroute} */
export async function reroute({ url, fetch }) {
if (url.pathname === '/api/reroute') return;
const api = new URL('/api/reroute', url);
api.searchParams.set('pathname', url.pathname);
const result = await fetch(api).then(r => r.json());
return result.pathname;
}
```
> **Note**: `reroute` is pure/idempotent. Result is cached per unique URL on client.
### transport
Pass custom types across server/client boundary:
```js
/// file: src/hooks.js
import { Vector } from '$lib/math';
/** @type {import('@sveltejs/kit').Transport} */
export const transport = {
Vector: {
encode: (value) => value instanceof Vector && [value.x, value.y],
decode: ([x, y]) => new Vector(x, y)
}
};
```

View File

@@ -0,0 +1,9 @@
# Icons
## CSS Approach
Use Iconify's CSS-based icons from [popular icon sets](https://icon-sets.iconify.design/). Supports [Tailwind CSS](https://iconify.design/docs/usage/css/tailwind/) and [UnoCSS](https://iconify.design/docs/usage/css/unocss/) plugins. No imports needed in `.svelte` files.
## Svelte Libraries
**Avoid** libraries with one `.svelte` file per icon - thousands of `.svelte` files severely slow Vite's dependency optimization, especially with mixed umbrella/subpath imports.
See [icon libraries](/packages#icons) for options.

View File

@@ -0,0 +1,138 @@
# Images
## Vite Built-in Handling
Vite automatically processes imported assets, adding hashes for caching and inlining small assets.
```svelte
<script>
import logo from '$lib/assets/logo.png';
</script>
<img alt="The project logo" src={logo} />
```
## @sveltejs/enhanced-img
Plugin that generates multiple formats (avif, webp), sizes, strips EXIF, and prevents layout shift. Only optimizes files available at build time.
### Setup
```sh
npm i -D @sveltejs/enhanced-img
```
```js
import { sveltekit } from '@sveltejs/kit/vite';
import { enhancedImages } from '@sveltejs/enhanced-img';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
enhancedImages(), // must come before SvelteKit plugin
sveltekit()
]
});
```
Build output cached in `./node_modules/.cache/imagetools`.
### Basic Usage
```svelte
<enhanced:img src="./path/to/your/image.jpg" alt="An alt text" />
```
Generates `<picture>` with multiple formats/sizes. Provide images at 2x resolution for HiDPI displays.
**Note:** Use `enhanced\:img` in CSS selectors to escape the colon.
### Dynamic Images
```svelte
<script>
import MyImage from './path/to/your/image.jpg?enhanced';
</script>
<enhanced:img src={MyImage} alt="some alt text" />
```
With `import.meta.glob`:
```svelte
<script>
const imageModules = import.meta.glob(
'/path/to/assets/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp,svg}',
{
eager: true,
query: {
enhanced: true
}
}
)
</script>
{#each Object.entries(imageModules) as [_path, module]}
<enhanced:img src={module.default} alt="some alt text" />
{/each}
```
### Dimensions
`width` and `height` auto-inferred to prevent layout shift. Override with CSS:
```svelte
<style>
.hero-image img {
width: var(--size);
height: auto;
}
</style>
```
### srcset and sizes
For large images, specify `sizes` to serve smaller versions on smaller devices:
```svelte
<enhanced:img src="./image.png" sizes="min(1280px, 100vw)"/>
```
Custom widths with `w` parameter (smallest auto-generated is 540px):
```svelte
<enhanced:img
src="./image.png?w=1280;640;400"
sizes="(min-width:1920px) 1280px, (min-width:1080px) 640px, (min-width:768px) 400px"
/>
```
Without `sizes`, generates 2x and 1x resolution images.
### Per-image Transforms
Apply transforms via query string:
```svelte
<enhanced:img src="./path/to/your/image.jpg?blur=15" alt="An alt text" />
```
[Full directive list](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/directives.md).
## Dynamic CDN Images
For images not available at build time (CMS, backend), use CDNs. Libraries:
- [`@unpic/svelte`](https://unpic.pics/img/svelte/) - CDN-agnostic
- [Cloudinary](https://svelte.cloudinary.dev/)
- CMS-specific: Contentful, Storyblok, Contentstack
## Best Practices
- Mix solutions as needed per use case
- Serve via CDN to reduce latency
- Provide 2x resolution source images for HiDPI
- Use `sizes` for images larger than ~400px
- For LCP images: set `fetchpriority="high"`, avoid `loading="lazy"`
- `width`/`height` prevent layout shift (auto-added by enhanced-img)
- Always provide `alt` text (compiler warns if missing)
- Don't use `em`/`rem` in `sizes` or change their default size with CSS

View File

@@ -0,0 +1,42 @@
# Integrations
## `vitePreprocess`
Enables Vite-supported CSS flavors: PostCSS, SCSS, Less, Stylus, SugarSS. Included by default in TypeScript projects.
```js
// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [vitePreprocess()]
};
export default config;
```
**TypeScript notes:**
- Svelte 4: Requires preprocessor
- Svelte 5: Native support for type syntax only. For complex TS syntax, use `vitePreprocess({ script: true })`
## Add-ons
Run `npx sv add` to setup integrations:
- prettier, eslint, vitest, playwright
- lucia (auth), tailwind, drizzle (DB)
- paraglide (i18n), mdsvex (markdown), storybook
## Packages
See [packages page](/packages) or [sveltesociety.dev](https://sveltesociety.dev/) for libraries and resources.
## `svelte-preprocess`
Alternative to `vitePreprocess` with additional features: Pug, Babel, global styles. May be slower and need more config. CoffeeScript not supported.
Install: `npm i -D svelte-preprocess` + add to `svelte.config.js` + install language library (e.g., `npm i -D sass`)
## Vite Plugins
Use any Vite plugin. See [`vitejs/awesome-vite`](https://github.com/vitejs/awesome-vite?tab=readme-ov-file#plugins).

View File

@@ -0,0 +1,27 @@
# SvelteKit Introduction
## What is SvelteKit?
Framework for building robust, performant web apps using Svelte. Similar to Next (React) or Nuxt (Vue).
## What is Svelte?
UI component framework. Compiler converts components to JavaScript (renders HTML) and CSS. Components handle user interfaces like navigation bars, forms, etc.
## SvelteKit vs Svelte
**Svelte**: Renders UI components only.
**SvelteKit**: Full-featured framework providing:
- Router
- [Build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) - minimal code loading
- [Offline support](service-workers)
- [Preloading](link-options#data-sveltekit-preload-data) - fetch before navigation
- [Configurable rendering](page-options):
- [SSR](glossary#SSR) - server-side rendering
- [CSR](glossary#CSR) - client-side rendering
- [Prerendering](glossary#Prerendering) - build-time rendering
- [Image optimization](images)
- [Vite](https://vitejs.dev/) + [HMR](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot) for instant dev updates
See [project types](project-types) for application examples.

View File

@@ -0,0 +1,104 @@
# Link Options
SvelteKit uses native `<a>` elements for navigation. Customize behavior with `data-sveltekit-*` attributes on links or parent elements. Also applies to `<form method="GET">`.
## data-sveltekit-preload-data
Preload page data on hover/touch before click happens.
**Values:**
- `"hover"` - preload on mouse rest over link (desktop) or `touchstart` (mobile)
- `"tap"` - preload on `touchstart` or `mousedown` only
**Default template:**
```html
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
```
**Override for specific links:**
```html
<a data-sveltekit-preload-data="tap" href="/stonks">
Get current stonk values
</a>
```
> Ignored if user has `navigator.connection.saveData` enabled.
## data-sveltekit-preload-code
Preload route code without data.
**Values (decreasing eagerness):**
- `"eager"` - preload immediately
- `"viewport"` - preload when link enters viewport
- `"hover"` - preload on hover (code only)
- `"tap"` - preload on tap (code only)
> `viewport` and `eager` only work for links present immediately after navigation, not dynamically added links.
> Only takes effect if more eager than `data-sveltekit-preload-data`.
> Ignored if user has reduced data usage enabled.
## data-sveltekit-reload
Force full-page navigation instead of client-side routing.
```html
<a data-sveltekit-reload href="/path">Path</a>
```
> Links with `rel="external"` get same treatment and are ignored during prerendering.
## data-sveltekit-replacestate
Replace current history entry instead of creating new one.
```html
<a data-sveltekit-replacestate href="/path">Path</a>
```
## data-sveltekit-keepfocus
Prevent focus reset after navigation.
```html
<form data-sveltekit-keepfocus>
<input type="text" name="query">
</form>
```
> Avoid on links. Only use on elements that persist after navigation.
## data-sveltekit-noscroll
Prevent scroll to top (or `#hash`) after navigation.
```html
<a href="path" data-sveltekit-noscroll>Path</a>
```
## Disabling Options
Use `"false"` value to disable inherited attributes:
```html
<div data-sveltekit-preload-data>
<!-- these links will be preloaded -->
<a href="/a">a</a>
<a href="/b">b</a>
<a href="/c">c</a>
<div data-sveltekit-preload-data="false">
<!-- these links will NOT be preloaded -->
<a href="/d">d</a>
<a href="/e">e</a>
<a href="/f">f</a>
</div>
</div>
```
**Conditional attributes:**
```svelte
<div data-sveltekit-preload-data={condition ? 'hover' : false}>
```

View File

@@ -0,0 +1,433 @@
# Loading Data
`load` functions fetch data before rendering `+page.svelte` and `+layout.svelte` components.
## Page data
`+page.js` exports a `load` function whose return value is available via the `data` prop:
```js
/// file: src/routes/blog/[slug]/+page.js
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`
}
};
}
```
```svelte
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
/** @type {import('./$types').PageProps} */
let { data } = $props();
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
```
`+page.js` runs on both server and browser (unless `export const ssr = false`). For server-only `load` (private env vars, database access), use `+page.server.js`:
```js
/// file: src/routes/blog/[slug]/+page.server.js
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
```
## Layout data
`+layout.js` or `+layout.server.js` load data for layouts:
```js
/// file: src/routes/blog/[slug]/+layout.server.js
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
```
```svelte
<!--- file: src/routes/blog/[slug]/+layout.svelte --->
<script>
/** @type {import('./$types').LayoutProps} */
let { data, children } = $props();
</script>
<main>
{@render children()}
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.posts as post}
<li>
<a href="/blog/{post.slug}">
{post.title}
</a>
</li>
{/each}
</ul>
</aside>
```
Child components access parent layout data. If multiple `load` functions return the same key, the last one wins.
## page.data
Parent layouts can access page/child data via `page.data`:
```svelte
<!--- file: src/routes/+layout.svelte --->
<script>
import { page } from '$app/state';
</script>
<svelte:head>
<title>{page.data.title}</title>
</svelte:head>
```
## Universal vs server
**Server `load`** (`+page.server.js`, `+layout.server.js`):
- Always runs server-side
- Access to `cookies`, `locals`, `platform`, `request`, `clientAddress`
- Must return serializable data (devalue format)
**Universal `load`** (`+page.js`, `+layout.js`):
- Runs on server during SSR, then in browser during hydration
- Subsequent runs happen in browser
- Can return any values (custom classes, components)
- Has `data` property containing server `load` return value
**When to use:**
- Server: database access, private env vars
- Universal: external API calls without credentials, non-serializable returns
- Both: pass server data to universal `load` via `data` property
## Using URL data
### url
`URL` instance with `origin`, `hostname`, `pathname`, `searchParams`. `url.hash` unavailable during `load`.
### route
Current route directory relative to `src/routes`:
```js
/// file: src/routes/a/[b]/[...c]/+page.js
/** @type {import('./$types').PageLoad} */
export function load({ route }) {
console.log(route.id); // '/a/[b]/[...c]'
}
```
### params
Derived from `url.pathname` and `route.id`. For `/a/[b]/[...c]` and pathname `/a/x/y/z`:
```json
{
"b": "x",
"c": "y/z"
}
```
## Making fetch requests
Use provided `fetch` function with enhanced features:
- Inherits `cookie` and `authorization` headers on server
- Allows relative requests on server
- Internal requests go directly to handler (no HTTP overhead)
- Response captured and inlined during SSR
- Response read from HTML during hydration (no extra network request)
```js
/// file: src/routes/items/[id]/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params }) {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
}
```
Cookies passed through `fetch` only if target host is same as app or more specific subdomain.
## Cookies
Server `load` functions can get/set cookies:
```js
/// file: src/routes/+layout.server.js
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies }) {
const sessionid = cookies.get('sessionid');
return {
user: await db.getUser(sessionid)
};
}
```
## Headers
`setHeaders` sets response headers (server-side only):
```js
/// file: src/routes/products/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
const url = `https://cms.example.com/products.json`;
const response = await fetch(url);
setHeaders({
age: response.headers.get('age'),
'cache-control': response.headers.get('cache-control')
});
return response.json();
}
```
Can only set each header once. Use `cookies.set()` for `set-cookie`.
## Using parent data
Access parent `load` data with `await parent()`:
```js
/// file: src/routes/+layout.js
/** @type {import('./$types').LayoutLoad} */
export function load() {
return { a: 1 };
}
```
```js
/// file: src/routes/abc/+layout.js
/** @type {import('./$types').LayoutLoad} */
export async function load({ parent }) {
const { a } = await parent();
return { b: a + 1 };
}
```
```js
/// file: src/routes/abc/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ parent }) {
const { a, b } = await parent();
return { c: a + b };
}
```
**Avoid waterfalls:** Call independent operations before `await parent()`.
## Errors
Throw expected errors with `error` helper:
```js
/// file: src/routes/admin/+layout.server.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
error(401, 'not logged in');
}
if (!locals.user.isAdmin) {
error(403, 'not an admin');
}
}
```
Unexpected errors treated as 500 and invoke `handleError`.
## Redirects
Use `redirect` helper with 3xx status code:
```js
/// file: src/routes/user/+layout.server.js
import { redirect } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
redirect(307, '/login');
}
}
```
Don't use inside `try {...}` blocks. For client-side navigation outside `load`, use `goto` from `$app/navigation`.
## Streaming with promises
Server `load` promises stream to browser as they resolve:
```js
/// file: src/routes/blog/[slug]/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
comments: loadComments(params.slug), // streams
post: await loadPost(params.slug) // awaited
};
}
```
```svelte
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
/** @type {import('./$types').PageProps} */
let { data } = $props();
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
{#await data.comments}
Loading comments...
{:then comments}
{#each comments as comment}
<p>{comment.content}</p>
{/each}
{:catch error}
<p>error loading comments: {error.message}</p>
{/await}
```
Handle rejections to avoid unhandled promise errors. Attach noop-`catch` or use SvelteKit's `fetch`.
**Limitations:**
- Doesn't work without JavaScript
- Can't `setHeaders` or redirect inside streamed promises
- Not supported on some platforms (AWS Lambda, Firebase)
## Parallel loading
All `load` functions run concurrently. Server `load` results grouped into single response during client navigation.
## Rerunning load functions
SvelteKit tracks dependencies to avoid unnecessary reruns.
**A `load` function reruns when:**
- Referenced `params` property changes
- Referenced `url` property changes (`pathname`, `search`, etc.)
- Calls `url.searchParams.get/getAll/has` and parameter changes
- Calls `await parent()` and parent reruns
- Declared dependency via `fetch` or `depends` and URL invalidated with `invalidate(url)`
- `invalidateAll()` called
```js
/// file: src/routes/random-number/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, depends }) {
const response = await fetch('https://api.example.com/random-number');
depends('app:random');
return {
number: await response.json()
};
}
```
```svelte
<!--- file: src/routes/random-number/+page.svelte --->
<script>
import { invalidate, invalidateAll } from '$app/navigation';
/** @type {import('./$types').PageProps} */
let { data } = $props();
function rerunLoadFunction() {
invalidate('app:random');
invalidate('https://api.example.com/random-number');
invalidate(url => url.href.includes('random-number'));
invalidateAll();
}
</script>
<p>random number: {data.number}</p>
<button onclick={rerunLoadFunction}>Update random number</button>
```
### Untracking dependencies
Exclude from tracking with `untrack`:
```js
/// file: src/routes/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ untrack, url }) {
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
}
```
## Implications for authentication
- Layout `load` doesn't run on every request (e.g., client-side navigation)
- Layout and page `load` run concurrently unless `await parent()` called
**Auth strategies:**
- Use hooks to protect routes before `load` runs
- Put auth guards in `+page.server.js` for route-specific protection
- Avoid auth in `+layout.server.js` unless all children call `await parent()`
## Using `getRequestEvent`
Access request event in shared logic:
```js
/// file: src/lib/server/auth.js
import { redirect } from '@sveltejs/kit';
import { getRequestEvent } from '$app/server';
export function requireLogin() {
const { locals, url } = getRequestEvent();
if (!locals.user) {
const redirectTo = url.pathname + url.search;
const params = new URLSearchParams({ redirectTo });
redirect(307, `/login?${params}`);
}
return locals.user;
}
```
```js
/// file: +page.server.js
import { requireLogin } from '$lib/server/auth';
export function load() {
const user = requireLogin();
return {
message: `hello ${user.name}!`
};
}
```

View File

@@ -0,0 +1,140 @@
# SvelteKit v1 to v2 Migration Guide
Use `npx sv migrate sveltekit-2` to auto-migrate many changes.
## `redirect` and `error` no longer thrown
Don't `throw` these anymore — just call them:
```js
import { error } from '@sveltejs/kit'
// Before
throw error(500, 'something went wrong');
// After
error(500, 'something went wrong');
```
Inside `try` blocks, use `isHttpError` and `isRedirect` from `@sveltejs/kit` to distinguish them.
## `path` required for cookies
Must specify `path` when setting/deleting cookies. Usually `path: '/'`:
```js
export function load({ cookies }) {
cookies.set(name, value, { path: '/' });
return { response }
}
```
## Top-level promises not auto-awaited
Promises in `load` return values are no longer automatically awaited. Use `await` explicitly:
```js
// Single promise
export async function load({ fetch }) {
const response = await fetch(url).then(r => r.json());
return { response }
}
```
```js
// Multiple promises - use Promise.all to avoid waterfalls
export async function load({ fetch }) {
const [a, b] = await Promise.all([
fetch(url1).then(r => r.json()),
fetch(url2).then(r => r.json()),
]);
return { a, b };
}
```
## `goto()` changes
- No longer accepts external URLs (use `window.location.href = url`)
- `state` object now sets `$page.state` and must match `App.PageState` interface
## Paths relative by default
[`paths.relative`](configuration#paths) defaults to `true`. All paths (including `%sveltekit.assets%`, `base`, `assets`) are consistently relative or absolute based on this setting.
## Server fetches not trackable
`dangerZone.trackServerFetches` removed due to security concerns.
## `preloadCode` requires `base` prefix
Both [`preloadCode`]($app-navigation#preloadCode) and [`preloadData`]($app-navigation#preloadData) now require `base` prefix if set. `preloadCode` takes single argument instead of multiple.
## `resolvePath` → `resolveRoute`
```js
// Before
import { resolvePath } from '@sveltejs/kit';
import { base } from '$app/paths';
const path = base + resolvePath('/blog/[slug]', { slug });
// After
import { resolveRoute } from '$app/paths';
const path = resolveRoute('/blog/[slug]', { slug });
```
## Improved error handling
`handleError` hook now receives `status` and `message` properties. Errors from your code have `status: 500` and `message: "Internal Error"`.
## Dynamic env vars unavailable during prerendering
`$env/dynamic/public` and `$env/dynamic/private` cannot be read during prerendering — use `$env/static/*` instead. Browser gets fresh values from `/_app/env.js`.
## `use:enhance` callback changes
`form` and `data` removed — use `formElement` and `formData` instead.
## File input forms require `multipart/form-data`
Forms with `<input type="file">` must have `enctype="multipart/form-data"` or SvelteKit throws error.
## Stricter `tsconfig.json`
Don't use `paths` or `baseUrl` in `tsconfig.json` — use [`alias` config](configuration#alias) in `svelte.config.js` instead.
## `getRequest` error handling
Errors for oversized `Content-Length` thrown when reading body, not when calling `getRequest`.
## `vitePreprocess` import change
Import from `@sveltejs/vite-plugin-svelte` instead of `@sveltejs/kit/vite`.
## Dependency requirements
- Node `18.13+`
- `svelte@4+`
- `vite@5+`
- `typescript@5+`
- `@sveltejs/vite-plugin-svelte@3+` (now peer dependency)
Generated `tsconfig.json` uses `"moduleResolution": "bundler"` and `verbatimModuleSyntax`.
## SvelteKit 2.12: `$app/stores` deprecated
Replace with `$app/state` (Svelte 5 runes-based). Provides fine-grained reactivity.
```svelte
<script>
// Before
import { page } from '$app/stores';
// After
import { page } from '$app/state';
</script>
<!-- Before -->
{$page.data}
<!-- After -->
{page.data}
```
Use `npx sv migrate app-state` to auto-migrate.

View File

@@ -0,0 +1,153 @@
# Migrating from Sapper to SvelteKit
## package.json Changes
### type: "module"
Add `"type": "module"` to `package.json`.
### dependencies
Remove `polka`, `express`, and middleware like `sirv` or `compression`.
### devDependencies
Replace `sapper` with `@sveltejs/kit` and an [adapter](adapters).
### scripts
- `sapper build``vite build` (with Node adapter)
- `sapper export``vite build` (with static adapter)
- `sapper dev``vite dev`
- `node __sapper__/build``node build`
## Project Files
### Configuration
Replace `webpack.config.js`/`rollup.config.js` with `svelte.config.js`. Move preprocessor options to `config.preprocess`. Add an [adapter](adapters).
For non-Vite filetypes, add Vite equivalents to [Vite config](project-structure#Project-files-vite.config.js).
### src/client.js
No equivalent. Move custom logic to `+layout.svelte` inside `onMount`.
### src/server.js
Use [custom server](adapter-node#Custom-server) with `adapter-node`, otherwise no equivalent (serverless environments).
### src/service-worker.js
`@sapper/service-worker` → [`$service-worker`]($service-worker):
- `files` unchanged
- `routes` removed
- `shell``build`
- `timestamp``version`
### src/template.html
Rename to `src/app.html`.
Remove `%sapper.base%`, `%sapper.scripts%`, `%sapper.styles%`.
- `%sapper.head%``%sveltekit.head%`
- `%sapper.html%``%sveltekit.body%`
- Remove `<div id="sapper">`
### src/node_modules
Use [`src/lib`]($lib) instead (Vite incompatible).
## Pages and Layouts
### Renamed Files
| Old | New |
|-----|-----|
| routes/about/index.svelte | routes/about/+page.svelte |
| routes/about.svelte | routes/about/+page.svelte |
| _error.svelte | +error.svelte |
| _layout.svelte | +layout.svelte |
### Imports
- `@sapper/app`: `goto`, `prefetch`, `prefetchRoutes` → [`$app/navigation`]($app-navigation): `goto`, `preloadData`, `preloadCode`
- `src/node_modules/*` → [`$lib`]($lib)
### Preload
`preload` → [`load`](load) in `+page.js`/`+layout.js`.
**API changes:**
- Single `event` argument (not `page` and `session`)
- No `this` object
- `this.fetch``fetch` from input
- `this.error`/`this.redirect` → throw [`error`](load#Errors)/[`redirect`](load#Redirects)
### Stores
```js
// Old
import { stores } from '@sapper/app';
const { preloading, page, session } = stores();
```
**New:** Import directly from [`$app/stores`]($app-stores) (or [`$app/state`]($app-state) for Svelte 5 + SvelteKit 2.12+):
- `preloading``navigating` (with `from`/`to` properties)
- `page` now has `url` and `params` (no `path` or `query`)
### Routing
Regex routes removed. Use [advanced route matching](advanced-routing#Matching).
### Segments
`segment` prop removed. Use `$page.url.pathname` instead.
### URLs
Relative URLs now resolve against current page, not base URL. Use root-relative URLs (`/...`) for context-independence.
### &lt;a&gt; Attributes
- `sapper:prefetch``data-sveltekit-preload-data`
- `sapper:noscroll``data-sveltekit-noscroll`
## Endpoints
No direct `req`/`res` access. Update to new signature for platform-agnostic behavior.
`fetch` now globally available (no need for `node-fetch`).
## Integrations
### HTML Minifier
Not included by default. Add as prod dependency and use via [hook](hooks#Server-hooks-handle):
```js
// @filename: ambient.d.ts
/// <reference types="@sveltejs/kit" />
declare module 'html-minifier';
// @filename: index.js
// ---cut---
import { minify } from 'html-minifier';
import { building } from '$app/environment';
const minification_options = {
collapseBooleanAttributes: true,
collapseWhitespace: true,
conservativeCollapse: true,
decodeEntities: true,
html5: true,
ignoreCustomComments: [/^#/],
minifyCSS: true,
minifyJS: false,
removeAttributeQuotes: true,
removeComments: false, // some hydration code needs comments, so leave them in
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
sortAttributes: true,
sortClassName: true
};
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
let page = '';
return resolve(event, {
transformPageChunk: ({ html, done }) => {
page += html;
if (done) {
return building ? minify(page, minification_options) : page;
}
}
});
}
```
**Note:** `prerendering` is `false` with `vite preview`, so inspect built HTML files directly to verify minification.

View File

@@ -0,0 +1,86 @@
# OpenTelemetry Tracing
**Available since 2.31** - Experimental feature
## Setup
Enable in `svelte.config.js`:
```js
/// file: svelte.config.js
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
experimental: {
tracing: {
server: true
},
instrumentation: {
server: true
}
}
}
};
export default config;
```
## What Gets Traced
- `handle` hook and `sequence` functions
- Server `load` functions (including universal `load` on server)
- Form actions
- Remote functions
## Augmenting Spans
Access `root` and `current` spans via request event:
```js
/// file: $lib/authenticate.ts
import { getRequestEvent } from '$app/server';
import { getAuthenticatedUser } from '$lib/auth-core';
async function authenticate() {
const user = await getAuthenticatedUser();
const event = getRequestEvent();
event.tracing.root.setAttribute('userId', user.id);
}
```
## Local Development Setup
Install dependencies:
```sh
npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-proto import-in-the-middle
```
Create `src/instrumentation.server.js`:
```js
/// file: src/instrumentation.server.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { createAddHookMessageChannel } from 'import-in-the-middle';
import { register } from 'node:module';
const { registerOptions } = createAddHookMessageChannel();
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);
const sdk = new NodeSDK({
serviceName: 'test-sveltekit-tracing',
traceExporter: new OTLPTraceExporter(),
instrumentations: [getNodeAutoInstrumentations()]
});
sdk.start();
```
View traces at [localhost:16686](http://localhost:16686) with Jaeger.
## Notes
- `src/instrumentation.server.ts` runs before app code (if platform/adapter supports it)
- Tracing has performance overhead - consider dev/preview only
- `@opentelemetry/api` is optional peer dependency, usually satisfied by SDK packages

View File

@@ -0,0 +1,138 @@
# Packaging Svelte Libraries
Use `@sveltejs/package` to build component libraries. Structure is same as SvelteKit apps, but `src/lib` is public-facing (not `src/routes`).
## svelte-package Command
Generates `dist` directory from `src/lib`:
- Preprocesses Svelte components
- Transpiles TypeScript to JavaScript
- Generates `.d.ts` type definitions (requires `typescript >= 4.0.0`)
## package.json Configuration
### Key Fields
**name** - Package identifier for npm
```json
{ "name": "your-library" }
```
**license** - Usage terms (e.g., `"MIT"`)
**files** - What gets published (README/LICENSE auto-included)
```json
{ "files": ["dist"] }
```
**exports** - Entry points with conditions
```json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
}
}
}
```
Export conditions:
- `types` - TypeScript definitions
- `svelte` - For Svelte-aware tooling (use `default` for non-Svelte libraries)
Multiple entry points:
```json
{
"exports": {
"./Foo.svelte": {
"types": "./dist/Foo.svelte.d.ts",
"svelte": "./dist/Foo.svelte"
}
}
}
```
**svelte** - Legacy field for backwards compatibility
```json
{ "svelte": "./dist/index.js" }
```
**sideEffects** - Enables tree-shaking. Mark CSS files as side effects for webpack compatibility:
```json
{ "sideEffects": ["**/*.css"] }
```
## TypeScript
Type definitions auto-generated by default. For non-root exports (e.g., `your-library/foo`), TypeScript won't resolve `types` condition correctly. Two solutions:
**Option 1:** Users set `"moduleResolution": "bundler"` (TS 5+), `"node16"`, or `"nodenext"` in tsconfig
**Option 2:** Use `typesVersions` in package.json:
```json
{
"exports": {
"./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js"
}
},
"typesVersions": {
">4.0": {
"foo": ["./dist/foo.d.ts"]
}
}
}
```
## Best Practices
- Avoid SvelteKit-specific modules (`$app/*`) unless targeting only SvelteKit projects. Use `esm-env` instead
- Add aliases via `svelte.config.js` (not `vite.config.js`)
- Removing exports or export conditions is a breaking change
- Adding new exports is non-breaking
## Source Maps
Enable declaration maps for "Go to Definition" support:
1. Set `"declarationMap": true` in tsconfig.json
2. Publish source files alongside dist:
```json
{
"files": [
"dist",
"!dist/**/*.test.*",
"src/lib",
"!src/lib/**/*.test.*"
]
}
```
## CLI Options
```sh
svelte-package [options]
```
- `-w`/`--watch` - Rebuild on changes
- `-i`/`--input` - Input directory (default: `src/lib`)
- `-o`/`--output` - Output directory (default: `dist`)
- `-p`/`--preserve-output` - Don't empty output first (default: `false`)
- `-t`/`--types` - Generate type definitions (default: `true`)
- `--tsconfig` - Path to tsconfig/jsconfig
## Publishing
```sh
npm publish
```
## Caveats
**Relative imports must be fully specified** with extensions:
```js
import { something } from './something/index.js';
```
For TypeScript, import `.ts` files using `.js` extension. Set `"moduleResolution": "NodeNext"` in tsconfig for help.

View File

@@ -0,0 +1,170 @@
# Page Options
SvelteKit renders components on the server first, sends HTML to client, then hydrates for interactivity. Export options from `+page.js`, `+page.server.js`, `+layout.js`, or `+layout.server.js` to control behavior per page or page groups. Child layouts/pages override parent values.
## prerender
Generate static HTML at build time for routes that don't need dynamic rendering.
```js
/// file: +page.js/+page.server.js/+server.js
export const prerender = true;
```
Or prerender everything except specific pages:
```js
/// file: +page.js/+page.server.js/+server.js
export const prerender = false;
```
Use `'auto'` to prerender but keep in manifest for dynamic SSR:
```js
/// file: +page.js/+page.server.js/+server.js
export const prerender = 'auto';
```
Prerenderer starts at root and follows `<a>` links. Configure entry points via `config.kit.prerender.entries` or [`entries`](#entries) function.
During prerendering, `building` from `$app/environment` is `true`.
### Prerendering server routes
`+server.js` files inherit `prerender` from pages that fetch from them:
```js
/// file: +page.js
export const prerender = true;
/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
const res = await fetch('/my-server-route.json');
return await res.json();
}
```
### When not to prerender
**Rule:** Two users hitting a page directly must get identical server content.
- Don't prerender personalized content
- `url.searchParams` forbidden during prerender (use in browser only, e.g., `onMount`)
- Pages with [actions](form-actions) can't be prerendered
### Route conflicts
Avoid directory/file name conflicts. Use file extensions: `foo.json/+server.js` and `foo/bar.json/+server.js` create `foo.json` and `foo/bar.json`. Pages write `foo/index.html`.
### Troubleshooting
Error "routes marked as prerenderable, but were not prerendered" means crawler didn't reach the route. Fix by:
- Ensure links exist from `config.kit.prerender.entries` or [`entries`](#entries)
- Add links from other prerendered pages with SSR enabled
- Change to `export const prerender = 'auto'`
## entries
SvelteKit auto-discovers pages by crawling from entry points (non-dynamic routes). For dynamic routes without discoverable links, export `entries`:
```js
/// file: src/routes/blog/[slug]/+page.server.js
/** @type {import('./$types').EntryGenerator} */
export function entries() {
return [
{ slug: 'hello-world' },
{ slug: 'another-blog-post' }
];
}
export const prerender = true;
```
`entries` can be `async` for fetching from CMS/database.
## ssr
Disable server-side rendering to render empty shell (client-only):
```js
/// file: +page.js
export const ssr = false;
// If both `ssr` and `csr` are `false`, nothing will be rendered!
```
In root `+layout.js`, this turns app into SPA. Not recommended for most cases.
> **Note:** If page options are static values, SvelteKit evaluates them statically. Otherwise, it imports files on server. Browser-only code must not run on module load—import in `+page.svelte` or `+layout.svelte` instead.
## csr
Disable client-side rendering for pages not requiring JavaScript:
```js
/// file: +page.js
export const csr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!
```
**Effects:**
- No JavaScript shipped
- `<script>` tags removed from components
- `<form>` can't be progressively enhanced
- Full-page browser navigation
- HMR disabled
Enable in dev only:
```js
/// file: +page.js
import { dev } from '$app/environment';
export const csr = dev;
```
## trailingSlash
Control URL trailing slash behavior: `'never'` (default), `'always'`, or `'ignore'`.
```js
/// file: src/routes/+layout.js
export const trailingSlash = 'always';
```
Affects prerendering: `'always'` creates `about/index.html`, otherwise `about.html`.
> **Note:** Don't use `'ignore'`—relative paths differ (`./y` from `/x` vs `/x/`), and `/x` vs `/x/` are separate URLs (bad for SEO).
## config
Adapter-specific deployment configuration. Shape depends on adapter. Top-level merge only.
```js
/// file: src/routes/+page.js
/** @type {import('some-adapter').Config} */
export const config = {
runtime: 'edge'
};
```
**Merge example:**
```js
/// file: src/routes/+layout.js
export const config = {
runtime: 'edge',
regions: 'all',
foo: { bar: true }
}
```
```js
/// file: src/routes/+page.js
export const config = {
regions: ['us1', 'us2'],
foo: { baz: true }
}
```
**Result:** `{ runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }`

View File

@@ -0,0 +1,85 @@
# Performance
## Built-in Optimizations
SvelteKit automatically provides:
- Code-splitting
- Asset preloading
- File hashing for caching
- Request coalescing (groups server `load` functions into single HTTP request)
- Parallel loading for universal `load` functions
- Data inlining (replays server `fetch` in browser without new request)
- Conservative invalidation (only re-runs `load` when necessary)
- Prerendering
- Link preloading
## Diagnosing Issues
**Tools:**
- [PageSpeed Insights](https://pagespeed.web.dev/)
- [WebPageTest](https://www.webpagetest.org/)
- Browser devtools: Lighthouse, Network, Performance tabs
**Important:** Test in preview mode after building, not dev mode.
**Instrumenting:** Use [OpenTelemetry](https://opentelemetry.io/) or [Server-Timing headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) to debug slow API calls.
## Optimizing Assets
### Images
Use `@sveltejs/enhanced-img` package (see [images](images) page).
### Videos
- Compress with [Handbrake](https://handbrake.fr/), convert to `.webm` or `.mp4`
- Lazy-load below-the-fold videos: `preload="none"`
- Strip audio from muted videos with [FFmpeg](https://ffmpeg.org/)
### Fonts
SvelteKit doesn't preload fonts by default. Preload in `handle` hook with `resolve` and `preload` filter. [Subset fonts](https://web.dev/learn/performance/optimize-web-fonts#subset_your_web_fonts) to reduce size.
## Reducing Code Size
### Svelte Version
Use latest version. Svelte 5 < Svelte 4 < Svelte 3 (size and speed).
### Packages
Use [`rollup-plugin-visualizer`](https://www.npmjs.com/package/rollup-plugin-visualizer) to identify large packages. Inspect build output with `build: { minify: false }` in Vite config.
### External Scripts
Minimize third-party scripts. Use server-side analytics (Cloudflare, Netlify, Vercel). Run scripts in web worker with [Partytown's SvelteKit integration](https://partytown.builder.io/sveltekit).
### Selective Loading
Use dynamic `import(...)` for conditional code instead of static `import`.
## Navigation
### Preloading
Configure [link options](link-options) for eager preloading (default on `<body>`).
### Non-essential Data
Return promises in `load` functions for slow data. Server `load` functions will [stream](load#Streaming-with-promises) data after navigation.
### Preventing Waterfalls
**Waterfalls** = sequential requests that kill performance.
**Browser waterfalls:**
- SvelteKit adds `modulepreload` tags/headers automatically
- Check network tab for additional preload needs
- Manually handle web fonts
- **Avoid SPA mode** — creates waterfalls (empty page → JS → render)
**Backend waterfalls:**
- Use [server `load` functions](load#Universal-vs-server) to make backend requests from server, not browser
- Use database joins instead of sequential queries
## Hosting
- Locate frontend in same data center as backend
- Use edge deployment (many adapters support this, some [per-route](page-options#config))
- Use CDN for images (often automatic)
- Ensure HTTP/2 or newer for parallel file loading
## Further Reading
[Core Web Vitals](https://web.dev/explore/learn-core-web-vitals)

View File

@@ -0,0 +1,83 @@
# SvelteKit Project Structure
## Directory Structure
```tree
my-project/
├ src/
│ ├ lib/
│ │ ├ server/ # Server-only code
│ │ └ [lib files] # Utilities/components
│ ├ params/ # Param matchers
│ ├ routes/ # App routes
│ ├ app.html # Page template
│ ├ error.html # Error page
│ ├ hooks.client.js
│ ├ hooks.server.js
│ ├ service-worker.js
│ └ instrumentation.server.js
├ static/ # Static assets
├ tests/
├ package.json
├ svelte.config.js
├ tsconfig.json
└ vite.config.js
```
## Key Directories
### src/lib
- Import via `$lib` alias
- `lib/server`: Server-only code, import via `$lib/server`. Client imports are blocked.
### src/routes
Contains app routes and colocated components.
### src/params
Param matchers for advanced routing.
## Key Files
### app.html
Page template with placeholders:
- `%sveltekit.head%` - `<link>`, `<script>`, `<svelte:head>` content
- `%sveltekit.body%` - Rendered markup (must be inside `<div>`, not directly in `<body>`)
- `%sveltekit.assets%` - `paths.assets` or relative to `paths.base`
- `%sveltekit.nonce%` - CSP nonce
- `%sveltekit.env.[NAME]%` - Environment variables (must start with `publicPrefix`, usually `PUBLIC_`)
- `%sveltekit.version%` - App version
### error.html
Fallback error page with placeholders:
- `%sveltekit.status%` - HTTP status
- `%sveltekit.error.message%` - Error message
### hooks.client.js / hooks.server.js
Client and server hooks.
### service-worker.js
Service worker code.
### instrumentation.server.js
Observability setup. Runs before app code (requires adapter support).
### package.json
Must include `@sveltejs/kit`, `svelte`, `vite` as `devDependencies`.
Must have `"type": "module"` (`.cjs` for CommonJS).
### svelte.config.js
Svelte and SvelteKit configuration.
### tsconfig.json
TypeScript config. Extends generated `.svelte-kit/tsconfig.json`. Use `typescript.config` setting for top-level changes.
### vite.config.js
Vite config using `@sveltejs/kit/vite` plugin.
## Other
### static/
Static assets served as-is (e.g., `robots.txt`, `favicon.png`).
### .svelte-kit/
Generated files (configurable via `outDir`). Can be deleted and regenerated.

View File

@@ -0,0 +1,61 @@
# Rendering & Deployment
SvelteKit supports multiple rendering modes and deployment targets. Rendering is controlled by your adapter choice and configuration. Project structure and routing remain the same across all types.
## Default Rendering
- First page: [SSR](glossary#SSR) (server-side rendering)
- Subsequent pages: [CSR](glossary#CSR) (client-side rendering)
- Benefits: Better SEO, faster navigation, no flash between pages
- Also called "transitional apps"
## Static Site Generation (SSG)
- Use [`adapter-static`](adapter-static) to prerender entire site
- Or use [prerender option](page-options#prerender) for specific pages only
- For very large sites: Use [ISR with `adapter-vercel`](adapter-vercel#Incremental-Static-Regeneration)
- Can mix rendering types across pages
## Single-Page App (SPA)
- CSR only
- See [single-page apps guide](single-page-apps)
- Skip `server` file documentation if no backend needed
## Multi-Page App (MPA)
- Not typical for SvelteKit
- Use [`csr = false`](page-options#csr) to remove JS and force server rendering
- Or use [`data-sveltekit-reload`](link-options#data-sveltekit-reload) for specific server-rendered links
## Separate Backend
If backend is in another language (Go, Java, PHP, Ruby, Rust, C#):
- **Recommended**: Deploy frontend separately with `adapter-node` or serverless adapter
- Alternative: Deploy as SPA served by backend (worse SEO/performance)
- Skip `server` file docs
- See [FAQ on external backends](faq#How-do-I-use-a-different-backend-API-server)
## Deployment Targets
**Serverless**:
- [adapter-auto](adapter-auto) (zero config)
- [`adapter-vercel`](adapter-vercel), [`adapter-netlify`](adapter-netlify), [`adapter-cloudflare`](adapter-cloudflare)
- Some support `edge` option for [edge rendering](glossary#Edge)
**Your Server/VPS**: [`adapter-node`](adapter-node)
**Container**: [`adapter-node`](adapter-node) for Docker/LXC
**Library**: Use [`@sveltejs/package`](packaging) with [`sv create`](/docs/cli/sv-create)
**Offline/PWA**: Full [service worker](service-workers) support
**Mobile**: Convert SPA with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/) or [Capacitor](https://capacitorjs.com/solution/svelte)
- Use [`bundleStrategy: 'single'`](configuration#output) to reduce requests (helpful for HTTP/1 servers)
**Desktop**: Convert SPA with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/), [Wails](https://wails.io/docs/guides/sveltekit/), or [Electron](https://www.electronjs.org/)
**Browser Extension**: Use [`adapter-static`](adapter-static) or [community adapters](/packages#sveltekit-adapters)
**Embedded Devices**: Svelte runs on low-power devices. Use [`bundleStrategy: 'single'`](configuration#output) to limit concurrent connections

View File

@@ -0,0 +1,569 @@
# Remote Functions
**Available since 2.27** - Experimental feature for type-safe client-server communication.
## Setup
Enable in `svelte.config.js`:
```js
/// file: svelte.config.js
const config = {
kit: {
experimental: {
remoteFunctions: true
}
},
compilerOptions: {
experimental: {
async: true // optional, for await in components
}
}
};
```
## Overview
Export from `.remote.js` or `.remote.ts` files in `src/`. Four types: `query`, `form`, `command`, `prerender`. Client calls become `fetch` wrappers to generated HTTP endpoints.
---
## query
Read dynamic data from server:
```js
/// file: src/routes/blog/data.remote.js
import { query } from '$app/server';
import * as db from '$lib/server/database';
export const getPosts = query(async () => {
const posts = await db.sql`
SELECT title, slug FROM post ORDER BY published_at DESC
`;
return posts;
});
```
Use with `await`:
```svelte
<!--- file: src/routes/blog/+page.svelte --->
<script>
import { getPosts } from './data.remote';
</script>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
```
Or with properties:
```svelte
<script>
const query = getPosts();
</script>
{#if query.error}
<p>oops!</p>
{:else if query.loading}
<p>loading...</p>
{:else}
<ul>
{#each query.current as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
{/if}
```
### Query arguments
Validate with Standard Schema (Zod/Valibot):
```js
import * as v from 'valibot';
import { error } from '@sveltejs/kit';
import { query } from '$app/server';
export const getPost = query(v.string(), async (slug) => {
const [post] = await db.sql`SELECT * FROM post WHERE slug = ${slug}`;
if (!post) error(404, 'Not found');
return post;
});
```
```svelte
<script>
let { params } = $props();
const post = $derived(await getPost(params.slug));
</script>
```
Arguments and return values serialized with [devalue](https://github.com/sveltejs/devalue).
### Refreshing
```svelte
<button onclick={() => getPosts().refresh()}>
Check for new posts
</button>
```
Queries are cached: `getPosts() === getPosts()`.
---
## query.batch
Batches requests in same macrotask (solves n+1 problem):
```js
import * as v from 'valibot';
import { query } from '$app/server';
export const getWeather = query.batch(v.string(), async (cities) => {
const weather = await db.sql`SELECT * FROM weather WHERE city = ANY(${cities})`;
const lookup = new Map(weather.map(w => [w.city, w]));
return (city) => lookup.get(city);
});
```
---
## form
Write data to server:
```js
/// file: src/routes/blog/data.remote.js
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { form } from '$app/server';
import * as auth from '$lib/server/auth';
export const createPost = form(
v.object({
title: v.pipe(v.string(), v.nonEmpty()),
content: v.pipe(v.string(), v.nonEmpty())
}),
async ({ title, content }) => {
const user = await auth.getUser();
if (!user) error(401, 'Unauthorized');
const slug = title.toLowerCase().replace(/ /g, '-');
await db.sql`INSERT INTO post (slug, title, content) VALUES (${slug}, ${title}, ${content})`;
redirect(303, `/blog/${slug}`);
}
);
```
```svelte
<script>
import { createPost } from '../data.remote';
</script>
<form {...createPost}>
<button>Publish!</button>
</form>
```
Works without JS (native form submission) and progressively enhances.
### Fields
Use `.as(type)` for field attributes:
```svelte
<form {...createPost}>
<label>
<h2>Title</h2>
<input {...createPost.fields.title.as('text')} />
</label>
<label>
<h2>Write your post</h2>
<textarea {...createPost.fields.content.as('text')}></textarea>
</label>
<button>Publish!</button>
</form>
```
Supports nested objects, arrays, strings, numbers, booleans, `File`:
```js
const datingProfile = v.object({
name: v.string(),
photo: v.file(),
info: v.object({
height: v.number(),
likesDogs: v.optional(v.boolean(), false)
}),
attributes: v.array(v.string())
});
export const createProfile = form(datingProfile, (data) => { /* ... */ });
```
```svelte
<form {...createProfile} enctype="multipart/form-data">
<input {...name.as('text')} />
<input {...photo.as('file')} />
<input {...info.height.as('number')} />
<input {...info.likesDogs.as('checkbox')} />
<input {...attributes[0].as('text')} />
<button>submit</button>
</form>
```
For `radio`/`checkbox` groups, pass value as second arg:
```svelte
{#each ['windows', 'mac', 'linux'] as os}
<label>
<input {...survey.fields.operatingSystem.as('radio', os)}>
{os}
</label>
{/each}
```
Or use `select`:
```svelte
<select {...survey.fields.operatingSystem.as('select')}>
<option>windows</option>
<option>mac</option>
</select>
```
### Programmatic validation
Use `invalid` function in handler:
```js
export const buyHotcakes = form(
v.object({ qty: v.pipe(v.number(), v.minValue(1)) }),
async (data, invalid) => {
try {
await db.buy(data.qty);
} catch (e) {
if (e.code === 'OUT_OF_STOCK') {
invalid(invalid.qty(`we don't have enough hotcakes`));
}
}
}
);
```
### Validation
Show issues per field:
```svelte
{#each createPost.fields.title.issues() as issue}
<p class="issue">{issue.message}</p>
{/each}
<input {...createPost.fields.title.as('text')} />
```
Validate programmatically:
```svelte
<form {...createPost} oninput={() => createPost.validate()}>
```
Client-side preflight validation:
```svelte
<script>
import * as v from 'valibot';
const schema = v.object({
title: v.pipe(v.string(), v.nonEmpty()),
content: v.pipe(v.string(), v.nonEmpty())
});
</script>
<form {...createPost.preflight(schema)}>
```
All issues: `createPost.fields.allIssues()`.
### Getting/setting values
```svelte
<div class="preview">
<h2>{createPost.fields.title.value()}</h2>
</div>
```
Set values:
```svelte
<script>
createPost.fields.set({
title: 'My new blog post',
content: 'Lorem ipsum...'
});
</script>
```
### Sensitive data
Prefix with `_` to prevent sending back to client:
```svelte
<input {...register.fields._password.as('password')} />
```
### Single-flight mutations
Refresh specific queries instead of all:
```js
export const createPost = form(
v.object({/* ... */}),
async (data) => {
// ...
await getPosts().refresh();
// or: await getPost(post.id).set(result);
redirect(303, `/blog/${slug}`);
}
);
```
### Returns
Return data instead of redirecting:
```js
export const createPost = form(
v.object({/* ... */}),
async (data) => {
// ...
return { success: true };
}
);
```
```svelte
{#if createPost.result?.success}
<p>Successfully published!</p>
{/if}
```
### enhance
Customize submission:
```svelte
<form {...createPost.enhance(async ({ form, data, submit }) => {
try {
await submit();
form.reset();
showToast('Successfully published!');
} catch (error) {
showToast('Oh no! Something went wrong');
}
})}>
```
Client-driven single-flight mutations:
```ts
await submit().updates(getPosts());
```
With optimistic updates:
```ts
await submit().updates(
getPosts().withOverride((posts) => [newPost, ...posts])
);
```
### Multiple instances
Isolate forms in lists:
```svelte
{#each await getTodos() as todo}
{@const modify = modifyTodo.for(todo.id)}
<form {...modify}>
<button disabled={!!modify.pending}>save changes</button>
</form>
{/each}
```
### buttonProps
Different action per button:
```svelte
<form {...login}>
<button>login</button>
<button {...register.buttonProps}>register</button>
</form>
```
---
## command
Call from anywhere (not element-specific):
```js
import * as v from 'valibot';
import { query, command } from '$app/server';
export const getLikes = query(v.string(), async (id) => {
const [row] = await db.sql`SELECT likes FROM item WHERE id = ${id}`;
return row.likes;
});
export const addLike = command(v.string(), async (id) => {
await db.sql`UPDATE item SET likes = likes + 1 WHERE id = ${id}`;
});
```
```svelte
<button
onclick={async () => {
try {
await addLike(item.id);
} catch (error) {
showToast('Something went wrong!');
}
}}
>
add like
</button>
```
Cannot be called during render.
### Updating queries
In command:
```js
export const addLike = command(v.string(), async (id) => {
await db.sql`UPDATE item SET likes = likes + 1 WHERE id = ${id}`;
getLikes(id).refresh();
});
```
Or when calling:
```ts
await addLike(item.id).updates(getLikes(item.id));
```
With optimistic update:
```ts
await addLike(item.id).updates(
getLikes(item.id).withOverride((n) => n + 1)
);
```
---
## prerender
Invoked at build time for static data:
```js
import { prerender } from '$app/server';
export const getPosts = prerender(async () => {
const posts = await db.sql`SELECT title, slug FROM post ORDER BY published_at DESC`;
return posts;
});
```
Cached using Cache API, survives reloads, cleared on new deployment.
### Prerender arguments
```js
export const getPost = prerender(v.string(), async (slug) => {
const [post] = await db.sql`SELECT * FROM post WHERE slug = ${slug}`;
if (!post) error(404, 'Not found');
return post;
});
```
Specify inputs:
```js
export const getPost = prerender(
v.string(),
async (slug) => { /* ... */ },
{
inputs: () => ['first-post', 'second-post', 'third-post']
}
);
```
Allow dynamic calls:
```js
export const getPost = prerender(
v.string(),
async (slug) => { /* ... */ },
{
dynamic: true,
inputs: () => [/* ... */]
}
);
```
---
## Validation errors
Handle with `handleValidationError` hook:
```js
/// file: src/hooks.server.ts
export function handleValidationError({ event, issues }) {
return {
message: 'Nice try, hacker!'
};
}
```
Opt out with `'unchecked'`:
```ts
export const getStuff = query('unchecked', async ({ id }: { id: string }) => {
// no validation
});
```
---
## getRequestEvent
Access `RequestEvent` inside remote functions:
```ts
import { getRequestEvent, query } from '$app/server';
const getUser = query(() => {
const { cookies } = getRequestEvent();
return await findUser(cookies.get('session_id'));
});
```
**Note:** `route`, `params`, `url` relate to calling page, not endpoint. Queries don't re-run on navigation unless argument changes.
---
## Redirects
Use `redirect(...)` in `query`, `form`, `prerender`. **Not** in `command`.

View File

@@ -0,0 +1,237 @@
# Routing
SvelteKit uses **filesystem-based routing**. Routes are defined by directories in your codebase:
- `src/routes` → root route
- `src/routes/about``/about` route
- `src/routes/blog/[slug]` → route with parameter `slug`
## Key Rules
- All files run on server
- All files run on client except `+server` files
- `+layout` and `+error` files apply to subdirectories
## +page
### +page.svelte
Defines a page. Rendered on server (SSR) initially, then client (CSR) for navigation.
```svelte
<!--- file: src/routes/+page.svelte --->
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
```
Use `<a>` elements for navigation, not framework-specific components.
Receive data from `load` via `data` prop:
```svelte
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
/** @type {import('./$types').PageProps} */
let { data } = $props();
</script>
<h1>{data.title}</h1>
<div>{@html data.content}</div>
```
### +page.js
Export `load` function to fetch data. Runs on server (SSR) and client (navigation).
```js
/// file: src/routes/blog/[slug]/+page.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
error(404, 'Not found');
}
```
Can export page options: `prerender`, `ssr`, `csr`
### +page.server.js
Server-only `load` function. Use for database access or private env variables. Return value must be serializable.
```js
/// file: src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
const post = await getPostFromDatabase(params.slug);
if (post) {
return post;
}
error(404, 'Not found');
}
```
Can also export **actions** for writing data via `<form>` elements.
## +error
Customize error pages per route:
```svelte
<!--- file: src/routes/blog/[slug]/+error.svelte --->
<script>
import { page } from '$app/state';
</script>
<h1>{page.status}: {page.error.message}</h1>
```
SvelteKit walks up tree to find closest error boundary. If all fail, renders `src/error.html`.
**Note:** `+error.svelte` not used for errors in `handle` hook or `+server.js` handlers.
## +layout
### +layout.svelte
Shared UI across pages. Must include `{@render children()}` for page content.
```svelte
<!--- file: src/routes/+layout.svelte --->
<script>
let { children } = $props();
</script>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/settings">Settings</a>
</nav>
{@render children()}
```
Layouts can be nested. Each inherits parent layout by default.
### +layout.js
Load data for layouts:
```js
/// file: src/routes/settings/+layout.js
/** @type {import('./$types').LayoutLoad} */
export function load() {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' }
]
};
}
```
Layout data available to all child pages:
```svelte
<!--- file: src/routes/settings/profile/+page.svelte --->
<script>
/** @type {import('./$types').PageProps} */
let { data } = $props();
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
```
Can export page options as defaults for children.
### +layout.server.js
Server-only layout `load`. Change type to `LayoutServerLoad`.
## +server
API routes. Export HTTP verb functions (`GET`, `POST`, etc.) that return `Response` objects.
```js
/// file: src/routes/api/random-number/+server.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export function GET({ url }) {
const min = Number(url.searchParams.get('min') ?? '0');
const max = Number(url.searchParams.get('max') ?? '1');
const d = max - min;
if (isNaN(d) || d < 0) {
error(400, 'min and max must be numbers, and min must be less than max');
}
const random = min + Math.random() * d;
return new Response(String(random));
}
```
### Receiving data
```js
/// file: src/routes/api/add/+server.js
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
```
**Note:** Form actions are generally better for browser-to-server data submission.
### Fallback handler
Handles unhandled HTTP methods:
```js
/// file: src/routes/api/add/+server.js
/** @type {import('./$types').RequestHandler} */
export async function fallback({ request }) {
return text(`I caught your ${request.method} request!`);
}
```
### Content negotiation
`+server.js` can coexist with `+page` files:
- `PUT`/`PATCH`/`DELETE`/`OPTIONS` → always `+server.js`
- `GET`/`POST`/`HEAD` → page if `accept` header prioritizes `text/html`, else `+server.js`
## $types
SvelteKit generates `$types.d.ts` for type safety:
```svelte
<script>
/** @type {import('./$types').PageProps} */
let { data } = $props();
</script>
```
Types: `PageProps`, `LayoutProps`, `PageLoad`, `PageServerLoad`, `LayoutLoad`, `LayoutServerLoad`, `RequestHandler`
With proper IDE tooling, types can be omitted entirely—they're auto-inserted.
## Other files
Non-route files in route directories are ignored. Colocate components with routes or use `$lib` for shared modules.

View File

@@ -0,0 +1,107 @@
# SEO
## Out of the box
### SSR
Search engines index server-side rendered content more reliably. SvelteKit uses SSR by default—keep it enabled unless necessary to disable in [`handle`](hooks#Server-hooks-handle).
### Performance
[Core Web Vitals](https://web.dev/vitals/#core-web-vitals) impact search ranking. Use [hybrid rendering](glossary#Hybrid-app), [optimize images](images), and test with [PageSpeed Insights](https://pagespeed.web.dev/) or [Lighthouse](https://developers.google.com/web/tools/lighthouse). See [performance page](performance) for details.
### Normalized URLs
SvelteKit redirects trailing slashes based on [`trailingSlash`](page-options#trailingSlash) config to avoid duplicate URLs.
## Manual setup
### &lt;title&gt; and &lt;meta&gt;
Every page needs unique `<title>` and `<meta name="description">` in [`<svelte:head>`](../svelte/svelte-head). See [Lighthouse SEO audits](https://web.dev/lighthouse-seo/).
**Pattern:** Return SEO data from page [`load`](load) functions, use as [`page.data`]($app-state) in `<svelte:head>` in root [layout](routing#layout).
### Sitemaps
Create dynamic sitemaps with endpoints:
```js
/// file: src/routes/sitemap.xml/+server.js
export async function GET() {
return new Response(
`
<?xml version="1.0" encoding="UTF-8" ?>
<urlset
xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="https://www.w3.org/1999/xhtml"
xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
>
<!-- <url> elements go here -->
</urlset>`.trim(),
{
headers: {
'Content-Type': 'application/xml'
}
}
);
}
```
### AMP
For [AMP](https://amp.dev/) sites:
1. Set [`inlineStyleThreshold`](configuration#inlineStyleThreshold) to `Infinity`
2. Disable `csr` in root layout
3. Add `amp` to `app.html`
4. Transform HTML with `@sveltejs/amp` in `transformPageChunk`
```js
/// file: src/hooks.server.js
import * as amp from '@sveltejs/amp';
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
let buffer = '';
return await resolve(event, {
transformPageChunk: ({ html, done }) => {
buffer += html;
if (done) return amp.transform(buffer);
}
});
}
```
**Optional:** Remove unused CSS with [`dropcss`](https://www.npmjs.com/package/dropcss):
```js
/// file: src/hooks.server.js
// @errors: 2307
import * as amp from '@sveltejs/amp';
import dropcss from 'dropcss';
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
let buffer = '';
return await resolve(event, {
transformPageChunk: ({ html, done }) => {
buffer += html;
if (done) {
let css = '';
const markup = amp
.transform(buffer)
.replace('⚡', 'amp') // dropcss can't handle this character
.replace(/<style amp-custom([^>]*?)>([^]+?)<\/style>/, (match, attributes, contents) => {
css = contents;
return `<style amp-custom${attributes}></style>`;
});
css = dropcss({ css, html: markup }).css;
return markup.replace('</style>', `${css}</style>`);
}
}
});
}
```
> Validate transformed HTML with `amphtml-validator` in `handle` hook, but only when prerendering (it's slow).

Some files were not shown because too many files have changed in this diff Show More