Skip to main content Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill sveltekit命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name sveltekit description SvelteKit - Full-stack Svelte framework with file-based routing, SSR/SSG, form actions, and adapters for deployment user-invocable false disable-model-invocation true version 1.0.0 category toolchain author Claude MPM Team license MIT progressive_disclosure {"entry_point":{"summary":"Full-stack Svelte framework: file-based routing, SSR/SSG/SPA, form actions, type-safe load functions, universal adapters","when_to_use":"Building full-stack web apps, SPAs with SSR, forms with progressive enhancement, API routes, SEO-critical sites","quick_start":"1. npm create svelte@latest 2. Define routes in src/routes/ 3. Use +page.svelte for pages 4. Deploy with adapters"},"token_estimate":{"entry":80,"full":5000}} tags ["sveltekit","svelte","ssr","ssg","routing","forms","fullstack","framework","adapters","typescript"] requires_tools []
SvelteKit - Full-Stack Svelte Framework
Overview
SvelteKit is the official full-stack framework for Svelte, providing file-based routing, server-side rendering (SSR), static site generation (SSG), form handling with progressive enhancement, and deployment adapters for any platform.
Key Features :
File-based routing : Automatic routes from src/routes/ directory structure
Load functions : Type-safe data fetching (+page.ts, +page.server.ts)
Form actions : Native form handling with progressive enhancement
SSR/SSG/SPA : Flexible rendering modes with per-route control
Adapters : Deploy to Vercel, Netlify, Node.js, Cloudflare, and more
TypeScript-first : Generated types from $types for type safety
Hooks : Middleware-like handle, handleError, handleFetch
API routes : +server.ts files for REST endpoints
Installation :
npm create svelte@latest my-app
cd my-app
npm install
npm run dev -- --open
Project Structure
Standard SvelteKit Layout
my-sveltekit-app/
├── src/
│ ├── routes/ # File-based routing
│ │ ├── +page.svelte # / (home page)
│ │ ├── +page.ts # Universal load function
│ │ ├── +page.server.ts # Server-only load function
│ │ ├── +layout.svelte # Shared layout
│ │ ├── +layout.ts # Layout load function
│ │ ├── +error.svelte # Error page
│ │ ├── about/
│ │ │ └── +page.svelte # /about
│ │ ├── blog/
│ │ │ ├── +page.svelte # /blog (list)
│ │ │ ├── +page.server.ts # Load posts
│ │ │ └── [slug]/
│ │ │ ├── +page.svelte # /blog/my-post
│ │ │ └── +page.server.ts
│ │ └── api/
│ │ └── posts/
│ │ └── +server.ts # GET /api/posts
│ ├── lib/
│ │ ├── components/
│ │ ├── server/ # Server-only utilities
│ │ │ └── database.ts
│ │ ├── stores/
│ │ └── utils/
│ ├── hooks.server.ts # Server hooks
│ ├── hooks.client.ts # Client hooks
│ ├── app.html # HTML template
│ └── app.d.ts # TypeScript declarations
├── static/ # Static assets (robots.txt, favicon)
├── tests/ # Playwright tests
├── svelte.config.js # SvelteKit configuration
├── vite.config.ts # Vite configuration
└── package.json
File-Based Routing
Route Conventions File naming determines routing :
File Route Purpose +page.svelte/Page component +page.ts- Universal load (client + server) +page.server.ts- Server-only load +layout.svelte- Shared layout +layout.ts- Layout load +layout.server.ts- Server layout load +server.ts/api/...API endpoint (GET/POST/etc) +error.svelte- Error boundary
Basic Routes src/routes/
├── +page.svelte # / (home)
├── about/
│ └── +page.svelte # /about
├── contact/
│ └── +page.svelte # /contact
└── pricing/
└── +page.svelte # /pricing
Dynamic Routes src/routes/
└── blog/
├── +page.svelte # /blog (list)
├── [slug]/
│ └── +page.svelte # /blog/my-post
└── [category]/
└── [slug]/
└── +page.svelte # /blog/tech/my-post
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data } = $props<{ data: PageData }>();
</script>
<article>
<h1>{data.post.title}</h1>
<p>{data.post.content}</p>
</article>
Optional Parameters src/routes/
└── archive/
└── [[year]]/
└── [[month]]/
└── +page.svelte # /archive, /archive/2024, /archive/2024/11
import type { PageLoad } from './$types' ;
export const load : PageLoad = async ({ params }) => {
const year = params.year || new Date ().getFullYear ();
const month = params.month || null ;
return {
year,
month,
posts : await fetchPosts ({ year, month })
};
};
Rest Parameters src/routes/
└── docs/
└── [...path]/
└── +page.svelte # /docs/guide/intro, /docs/api/reference
export const load : PageLoad = async ({ params }) => {
const path = params.path ;
const segments = path.split ('/' );
return {
doc : await fetchDoc (path)
};
};
Load Functions
Universal Load (+page.ts) Runs on both server and client. Must use fetch for data fetching.
import type { PageLoad } from './$types' ;
export const load : PageLoad = async ({ fetch, params, url }) => {
const response = await fetch ('/api/products' );
const products = await response.json ();
return {
products,
searchQuery : url.searchParams .get ('q' ) || ''
};
};
export const prerender = true ;
export const ssr = false ;
export const csr = true ;
Server-Only Load (+page.server.ts) Runs only on server. Direct database access allowed.
import { redirect } from '@sveltejs/kit' ;
import type { PageServerLoad } from './$types' ;
import { db } from '$lib/server/database' ;
export const load : PageServerLoad = async ({ locals, cookies }) => {
if (!locals.user ) {
throw redirect (303 , '/login' );
}
const stats = await db.query .stats .findFirst ({
where : eq (stats.userId , locals.user .id )
});
const apiKey = process.env .SECRET_API_KEY ;
const data = await fetchPrivateData (apiKey);
return {
stats,
userData : data
};
};
Streaming with Promises
import type { PageServerLoad } from './$types' ;
export const load : PageServerLoad = async () => {
return {
featured : await db.posts .findMany ({ where : { featured : true } }),
recent : db.posts .findMany ({ orderBy : { createdAt : 'desc' } }),
popular : db.posts .findMany ({ orderBy : { views : 'desc' } })
};
};
<!-- src/routes/posts/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data } = $props<{ data: PageData }>();
</script>
<h2>Featured</h2>
{#each data.featured as post}
<article>{post.title}</article>
{/each}
<h2>Recent</h2>
{#await data.recent}
<p>Loading recent posts...</p>
{:then posts}
{#each posts as post}
<article>{post.title}</article>
{/each}
{/await}
<h2>Popular</h2>
{#await data.popular}
<p>Loading popular posts...</p>
{:then posts}
{#each posts as post}
<article>{post.title}</article>
{/each}
{/await}
Layouts
Shared Layout <!-- src/routes/+layout.svelte -->
<script lang="ts">
import Header from '$lib/components/Header.svelte';
import Footer from '$lib/components/Footer.svelte';
import type { LayoutData } from './$types';
let { data, children } = $props<{ data: LayoutData, children: any }>();
</script>
<div class="app">
<Header user={data.user} />
<main>
{@render children()}
</main>
<Footer />
</div>
<style>
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
}
</style>
Layout Load
import type { LayoutServerLoad } from './$types' ;
export const load : LayoutServerLoad = async ({ locals }) => {
return {
user : locals.user || null
};
};
Nested Layouts src/routes/
├── +layout.svelte # Root layout (all pages)
├── (app)/
│ ├── +layout.svelte # App layout (dashboard, settings)
│ ├── dashboard/
│ │ └── +page.svelte # Uses: root + app layouts
│ └── settings/
│ └── +page.svelte
└── (marketing)/
├── +layout.svelte # Marketing layout (about, pricing)
├── about/
│ └── +page.svelte # Uses: root + marketing layouts
└── pricing/
└── +page.svelte
Layout groups with (name) don't affect URL structure :
/dashboard not /(app)/dashboard
Breaking Out of Layouts <!-- src/routes/admin/+layout.svelte -->
<script>
let { children } = $props();
</script>
<div class="admin">
{@render children()}
</div>
<!-- src/routes/admin/login/+page@.svelte -->
<!-- @ breaks out to root layout, skipping admin layout -->
<form method="POST">
<input name="email" type="email" />
<input name="password" type="password" />
<button type="submit">Login</button>
</form>
Form Actions
Basic Form Actions
import { fail, redirect } from '@sveltejs/kit' ;
import type { Actions } from './$types' ;
export const actions = {
default : async ({ request, cookies }) => {
const data = await request.formData ();
const email = data.get ('email' );
const password = data.get ('password' );
if (!email || !password) {
return fail (400 , { email, missing : true });
}
const user = await authenticateUser (email, password);
if (!user) {
return fail (401 , { email, incorrect : true });
}
cookies.set ('session' , user.sessionToken , {
path : '/' ,
httpOnly : true ,
sameSite : 'strict' ,
secure : process.env .NODE_ENV === 'production' ,
maxAge : 60 * 60 * 24 * 7
});
throw redirect (303 , '/dashboard' );
}
} satisfies Actions ;
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
import type { ActionData } from './$types';
let { form } = $props<{ form?: ActionData }>();
</script>
<form method="POST">
<input
name="email"
type="email"
value={form?.email ?? ''}
required
/>
<input name="password" type="password" required />
{#if form?.missing}
<p class="error">Please fill in all fields</p>
{/if}
{#if form?.incorrect}
<p class="error">Invalid email or password</p>
{/if}
<button type="submit">Log in</button>
</form>
Named Actions
import { fail } from '@sveltejs/kit' ;
import type { Actions , PageServerLoad } from './$types' ;
export const load : PageServerLoad = async () => {
return {
todos : await db.todos .findMany ()
};
};
export const actions = {
create : async ({ request }) => {
const data = await request.formData ();
const text = data.get ('text' );
if (!text) {
return fail (400 , { text, missing : true });
}
await db.todos .create ({ data : { text, done : false } });
return { success : true };
},
toggle : async ({ request }) => {
const data = await request.formData ();
const id = data.get ('id' );
const todo = await db.todos .findUnique ({ where : { id } });
await db.todos .update ({
where : { id },
data : { done : !todo.done }
});
return { toggled : true };
},
delete : async ({ request }) => {
const data = await request.formData ();
const id = data.get ('id' );
await db.todos .delete ({ where : { id } });
return { deleted : true };
}
} satisfies Actions ;
<!-- src/routes/todos/+page.svelte -->
<script lang="ts">
import type { PageData, ActionData } from './$types';
let { data, form } = $props<{ data: PageData, form?: ActionData }>();
</script>
<h1>Todos</h1>
{#if form?.success}
<p class="success">Todo created!</p>
{/if}
<form method="POST" action="?/create">
<input name="text" placeholder="What needs to be done?" required />
<button type="submit">Add</button>
</form>
{#each data.todos as todo}
<div>
<form method="POST" action="?/toggle">
<input type="hidden" name="id" value={todo.id} />
<input
type="checkbox"
checked={todo.done}
onchange={(e) => e.currentTarget.form?.requestSubmit()}
/>
<span class:done={todo.done}>{todo.text}</span>
</form>
<form method="POST" action="?/delete">
<input type="hidden" name="id" value={todo.id} />
<button type="submit">Delete</button>
</form>
</div>
{/each}
<style>
.done {
text-decoration: line-through;
opacity: 0.6;
}
</style>
Progressive Enhancement <!-- src/routes/search/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageData, ActionData } from './$types';
let { data, form } = $props<{ data: PageData, form?: ActionData }>();
let isLoading = $state(false);
</script>
<form
method="POST"
use:enhance={() => {
isLoading = true;
return async ({ update }) => {
await update();
isLoading = false;
};
}}
>
<input name="query" placeholder="Search..." />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Searching...' : 'Search'}
</button>
</form>
{#if form?.results}
<ul>
{#each form.results as result}
<li>{result.title}</li>
{/each}
</ul>
{/if}
API Routes (+server.ts)
REST Endpoints
import { json } from '@sveltejs/kit' ;
import type { RequestHandler } from './$types' ;
export const GET : RequestHandler = async ({ url }) => {
const limit = Number (url.searchParams .get ('limit' )) || 10 ;
const offset = Number (url.searchParams .get ('offset' )) || 0 ;
const posts = await db.posts .findMany ({
take : limit,
skip : offset,
orderBy : { createdAt : 'desc' }
});
return json (posts);
};
export const POST : RequestHandler = async ({ request, locals }) => {
if (!locals.user ) {
return json ({ error : 'Unauthorized' }, { status : 401 });
}
const data = await request.json ();
const post = await db.posts .create ({
data : {
title : data.title ,
content : data.content ,
authorId : locals.user .id
}
});
return json (post, { status : 201 });
};
Dynamic API Routes
import { error, json } from '@sveltejs/kit' ;
import type { RequestHandler } from './$types' ;
export const GET : RequestHandler = async ({ params }) => {
const post = await db.posts .findUnique ({
where : { id : params.id }
});
if (!post) {
throw error (404 , 'Post not found' );
}
return json (post);
};
export const PATCH : RequestHandler = async ({ params, request, locals }) => {
if (!locals.user ) {
throw error (401 , 'Unauthorized' );
}
const post = await db.posts .findUnique ({ where : { id : params.id } });
if (post.authorId !== locals.user .id ) {
throw error (403 , 'Forbidden' );
}
const data = await request.json ();
const updated = await db.posts .update ({
where : { id : params.id },
data : { title : data.title , content : data.content }
});
return json (updated);
};
export const DELETE : RequestHandler = async ({ params, locals }) => {
if (!locals.user ) {
throw error (401 , 'Unauthorized' );
}
const post = await db.posts .findUnique ({ where : { id : params.id } });
if (post.authorId !== locals.user .id ) {
throw error (403 , 'Forbidden' );
}
await db.posts .delete ({ where : { id : params.id } });
return new Response (null , { status : 204 });
};
Hooks
Server Hooks (hooks.server.ts)
import { redirect, type Handle } from '@sveltejs/kit' ;
import { sequence } from '@sveltejs/kit/hooks' ;
const auth : Handle = async ({ event, resolve }) => {
const sessionToken = event.cookies .get ('session' );
if (sessionToken) {
event.locals .user = await getUserFromSession (sessionToken);
}
return resolve (event);
};
const logging : Handle = async ({ event, resolve }) => {
const start = Date .now ();
const response = await resolve (event);
const duration = Date .now () - start;
console .log (`${event.request.method} ${event.url.pathname} ${response.status} ${duration} ms` );
return response;
};
const protect : Handle = async ({ event, resolve }) => {
if (event.url .pathname .startsWith ('/admin' )) {
if (!event.locals .user ?.isAdmin ) {
throw redirect (303 , '/login' );
}
}
if (event.url .pathname .startsWith ('/dashboard' )) {
if (!event.locals .user ) {
throw redirect (303 , '/login' );
}
}
return resolve (event);
};
export const handle = sequence (auth, logging, protect);
export const handleError = async ({ error, event } ) => {
console .error ('Error:' , error);
return {
message : 'An unexpected error occurred' ,
code : error?.code ?? 'UNKNOWN'
};
};
export const handleFetch = async ({ request, fetch } ) => {
if (request.url .startsWith ('https://api.example.com' )) {
request.headers .set ('Authorization' , `Bearer ${process.env.API_TOKEN} ` );
}
return fetch (request);
};
Client Hooks (hooks.client.ts)
import type { HandleClientError } from '@sveltejs/kit' ;
export const handleError : HandleClientError = async ({ error, event }) => {
console .error ('Client error:' , error);
if (typeof window !== 'undefined' ) {
}
return {
message : 'Something went wrong' ,
};
};
Environment Variables
Static Environment Variables
import { env } from '$env/static/public' ;
import { env as privateEnv } from '$env/static/private' ;
export const PUBLIC_API_URL = env.PUBLIC_API_URL ;
export const PUBLIC_SITE_NAME = env.PUBLIC_SITE_NAME ;
export const DATABASE_URL = privateEnv.DATABASE_URL ;
export const SECRET_KEY = privateEnv.SECRET_KEY ;
Dynamic Environment Variables
import { env } from '$env/dynamic/private' ;
import type { PageServerLoad } from './$types' ;
export const load : PageServerLoad = async () => {
const apiUrl = env.API_URL ;
return {
data : await fetch (apiUrl).then (r => r.json ())
};
};
PUBLIC_API_URL=https://api.example.com
PUBLIC_ANALYTICS_ID=UA-123456789
DATABASE_URL=postgres://localhost:5432/mydb
SECRET_KEY=super-secret-key
STRIPE_SECRET_KEY=sk_live_abc123
Prerendering and SSR
Prerendering Options
export const prerender = true ;
export const ssr = true ;
export const csr = true ;
export default {
kit : {
prerender : {
entries : ['*' ],
crawl : true
}
}
};
Dynamic Prerendering
import type { EntryGenerator , PageServerLoad } from './$types' ;
export const load : PageServerLoad = async ({ params }) => {
const post = await db.posts .findUnique ({
where : { slug : params.slug }
});
return { post };
};
export const entries : EntryGenerator = async () => {
const posts = await db.posts .findMany ();
return posts.map (post => ({
slug : post.slug
}));
};
export const prerender = true ;
Adapters
Vercel Adapter npm install -D @sveltejs/adapter-vercel
import adapter from '@sveltejs/adapter-vercel' ;
export default {
kit : {
adapter : adapter ({
runtime : 'edge' ,
regions : ['iad1' , 'sfo1' ],
split : false
})
}
};
Netlify Adapter npm install -D @sveltejs/adapter-netlify
import adapter from '@sveltejs/adapter-netlify' ;
export default {
kit : {
adapter : adapter ({
edge : false ,
split : false
})
}
};
Node Adapter npm install -D @sveltejs/adapter-node
import adapter from '@sveltejs/adapter-node' ;
export default {
kit : {
adapter : adapter ({
out : 'build' ,
precompress : true ,
envPrefix : 'MY_'
})
}
};
Static Adapter (SSG) npm install -D @sveltejs/adapter-static
import adapter from '@sveltejs/adapter-static' ;
export default {
kit : {
adapter : adapter ({
pages : 'build' ,
assets : 'build' ,
fallback : '200.html' ,
precompress : false
})
}
};
Cloudflare Pages npm install -D @sveltejs/adapter-cloudflare
import adapter from '@sveltejs/adapter-cloudflare' ;
export default {
kit : {
adapter : adapter ({
routes : {
include : ['/*' ],
exclude : ['<build>' ]
}
})
}
};
Testing
Unit Tests with Vitest
import { describe, it, expect } from 'vitest' ;
import { formatDate } from './utils' ;
describe ('formatDate' , () => {
it ('formats date correctly' , () => {
const date = new Date ('2024-01-15' );
expect (formatDate (date)).toBe ('January 15, 2024' );
});
});
Component Tests
import { render, fireEvent } from '@testing-library/svelte' ;
import { describe, it, expect, vi } from 'vitest' ;
import Button from './Button.svelte' ;
describe ('Button' , () => {
it ('renders with text' , () => {
const { getByText } = render (Button , {
props : { text : 'Click me' }
});
expect (getByText ('Click me' )).toBeInTheDocument ();
});
it ('calls onclick handler' , async () => {
const handleClick = vi.fn ();
const { getByText } = render (Button , {
props : { text : 'Click me' , onclick : handleClick }
});
const button = getByText ('Click me' );
await fireEvent.click (button);
expect (handleClick).toHaveBeenCalledOnce ();
});
});
E2E Tests with Playwright
import { expect, test } from '@playwright/test' ;
test ('user can log in' , async ({ page }) => {
await page.goto ('/login' );
await page.fill ('input[name="email"]' , 'user@example.com' );
await page.fill ('input[name="password"]' , 'password123' );
await page.click ('button[type="submit"]' );
await expect (page).toHaveURL ('/dashboard' );
await expect (page.locator ('h1' )).toContainText ('Dashboard' );
});
test ('login validation works' , async ({ page }) => {
await page.goto ('/login' );
await page.click ('button[type="submit"]' );
await expect (page.locator ('.error' )).toContainText ('Please fill in all fields' );
});
Load Function Tests
import { describe, it, expect, vi } from 'vitest' ;
import { load } from './+page.server' ;
vi.mock ('$lib/server/database' , () => ({
db : {
posts : {
findMany : vi.fn (() => Promise .resolve ([
{ id : '1' , title : 'Post 1' },
{ id : '2' , title : 'Post 2' }
]))
}
}
}));
describe ('blog page load' , () => {
it ('loads posts' , async () => {
const result = await load ({ params : {}, url : new URL ('http://localhost' ) } as any );
expect (result.posts ).toHaveLength (2 );
expect (result.posts [0 ].title ).toBe ('Post 1' );
});
});
Advanced Patterns
Parallel Loading
import type { LayoutServerLoad } from './$types' ;
export const load : LayoutServerLoad = async ({ locals }) => {
const [user, notifications, settings] = await Promise .all ([
db.users .findUnique ({ where : { id : locals.user .id } }),
db.notifications .findMany ({ where : { userId : locals.user .id } }),
db.settings .findUnique ({ where : { userId : locals.user .id } })
]);
return {
user,
notifications,
settings
};
};
Dependent Loading
import { error } from '@sveltejs/kit' ;
import type { PageServerLoad } from './$types' ;
export const load : PageServerLoad = async ({ params, parent }) => {
const { user } = await parent ();
const profile = await db.profiles .findUnique ({
where : { username : params.username }
});
if (!profile) {
throw error (404 , 'Profile not found' );
}
const posts = await db.posts .findMany ({
where : { authorId : profile.id },
orderBy : { createdAt : 'desc' }
});
return {
profile,
posts,
isOwnProfile : user?.id === profile.id
};
};
Invalidation and Reloading <script lang="ts">
import { invalidate, invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
async function refresh() {
// Reload current page data
await invalidateAll();
}
async function refreshPosts() {
// Reload specific data
await invalidate('/api/posts');
}
async function refreshUser() {
// Reload data depending on specific URL
await invalidate(url => url.pathname.startsWith('/api/user'));
}
</script>
<button onclick={refresh}>Refresh All</button>
<button onclick={refreshPosts}>Refresh Posts</button>
Page Options
export const ssr = false ;
export const csr = true ;
export const prerender = false ;
export const trailingSlash = 'always' ;
Deployment Examples
Vercel Deployment
npm install -g vercel
vercel login
vercel
vercel --prod
{
"buildCommand" : "npm run build" ,
"devCommand" : "npm run dev" ,
"framework" : "sveltekit"
}
Docker Deployment (Node Adapter) FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/build build/
COPY --from=builder /app/node_modules node_modules/
COPY package.json .
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "build"]
docker build -t my-sveltekit-app .
docker run -p 3000:3000 my-sveltekit-app
Static Hosting (Netlify, GitHub Pages) [build]
command = "npm run build"
publish = "build"
[[redirects]]
from = "/*"
to = "/200.html"
status = 200
Best Practices
Use +page.server.ts for sensitive operations - Keep secrets server-side
Leverage progressive enhancement - Forms work without JavaScript
Use $types for type safety - Auto-generated types from SvelteKit
Implement error boundaries - Use +error.svelte for graceful errors
Optimize images - Use @sveltejs/enhanced-img for automatic optimization
Enable prerendering - Static pages are faster and cheaper
Use parallel loading - Promise.all() for concurrent data fetching
Validate form data - Use Zod or similar for schema validation
Set security headers - Use hooks for CSP, CORS, etc.
Test with Playwright - E2E tests prevent regressions
Common Patterns
Authentication Flow
export const actions = {
default : async ({ request, cookies }) => {
const data = await request.formData ();
const user = await authenticate (data.get ('email' ), data.get ('password' ));
cookies.set ('session' , user.sessionToken , {
path : '/' ,
httpOnly : true ,
sameSite : 'strict' ,
secure : true ,
maxAge : 60 * 60 * 24 * 7
});
throw redirect (303 , '/dashboard' );
}
};
export const handle : Handle = async ({ event, resolve }) => {
const sessionToken = event.cookies .get ('session' );
if (sessionToken) {
event.locals .user = await getUserFromSession (sessionToken);
}
return resolve (event);
};
Protected Routes
import { redirect } from '@sveltejs/kit' ;
import type { PageServerLoad } from './$types' ;
export const load : PageServerLoad = async ({ locals }) => {
if (!locals.user ) {
throw redirect (303 , '/login' );
}
return {
user : locals.user
};
};
Form Validation
import { fail } from '@sveltejs/kit' ;
import { z } from 'zod' ;
import type { Actions } from './$types' ;
const schema = z.object ({
email : z.string ().email (),
password : z.string ().min (8 ),
confirmPassword : z.string ()
}).refine (data => data.password === data.confirmPassword , {
message : "Passwords don't match" ,
path : ['confirmPassword' ]
});
export const actions = {
default : async ({ request }) => {
const data = await request.formData ();
const formData = Object .fromEntries (data);
const result = schema.safeParse (formData);
if (!result.success ) {
return fail (400 , {
errors : result.error .flatten ().fieldErrors ,
data : formData
});
}
await createUser (result.data );
throw redirect (303 , '/login' );
}
} satisfies Actions ;
Resources
Summary
SvelteKit is the official full-stack framework for Svelte
File-based routing with +page.svelte, +layout.svelte, +server.ts
Load functions provide type-safe data fetching (universal and server-only)
Form actions enable progressive enhancement with native HTML forms
SSR/SSG/SPA modes with per-route control via prerender, ssr, csr
Adapters deploy to any platform (Vercel, Netlify, Node, Cloudflare, static)
Hooks provide middleware-like functionality for auth, logging, error handling
TypeScript-first with auto-generated $types for complete type safety
Environment variables with $env/static and $env/dynamic modules
Testing with Vitest (unit) and Playwright (E2E)