sveltekit-advanced
SvelteKit 高级功能指南 - 状态管理、远程函数(Remote Functions)、环境变量、Hooks、错误处理、链接选项、Service Workers、服务端模块、快照(快照/Shallow Routing)、$app/* 模块、$lib、$service-worker。
SvelteKit 高级功能指南 - 状态管理、远程函数(Remote Functions)、环境变量、Hooks、错误处理、链接选项、Service Workers、服务端模块、快照(快照/Shallow Routing)、$app/* 模块、$lib、$service-worker。
Advanced SvelteKit features reference. Covers state management across server/client, the remote functions API, env vars (legacy and explicit), hooks (server/shared/universal), error handling, link options, service workers, server-only modules, snapshots, shallow routing, and the $app/* modules.
Source: official SvelteKit llms.txt documentation.
Use this skill when you need to:
query/form/command/prerender.handle, handleFetch, handleError, handleValidationError, reroute, transport, init).App.Error.data-sveltekit-* attributes..server.* or $lib/server/.$app/* modules (forms, navigation, state, paths, server, environment, types).Do NOT use this skill for routing, form actions (legacy), load, basic setup, adapters configuration beyond env vars, or CSS/styling.
let user;) leak across requests. Authenticate via cookies, persist to DB.load functions — no side effects (no global stores). Return data instead.setContext('user', () => data.user) (pass a function so reactivity crosses boundaries). Reading context updated during SSR in a child does NOT propagate to the parent (it has already rendered).const derived from data only computes once. Use $derived(...) for values that should recompute when data changes.Opt in via svelte.config.js:
kit: { experimental: { remoteFunctions: true } },
compilerOptions: { experimental: { async: true } }
Flavors exported from *.remote.js:
| Flavor | Purpose | Key features |
|---|---|---|
| query | Read dynamic server data | Dedup, refresh(), loading/error/current |
| query.batch | Batch n+1 queries in one call | Returns (input, idx) => Output |
| query.live | Real-time async iterable | connected, reconnect(); first value serialized for SSR |
| form | Progressive-enhanced <form> | <form {...createPost}>, createPost.fields.x.as('text'), validate(), enhance(), for(id), preflight(schema) |
| command | Imperative mutation from event handlers | Cannot be called during render |
| prerender | Build-time data | inputs, dynamic: true |
getRequestEvent() works inside remote functions for cookies.redirect(...) allowed in query/form/prerender, NOT in command.getPosts().refresh() or getPost(id).set(...) in server handler; requested(getPosts, 1).refreshAll() for client-requested refreshes (limit is DoS protection).Legacy $env/* (default before SvelteKit 2.63):
| Module | Scope | Timing |
|---|---|---|
| $env/dynamic/private | Server only | Runtime (process.env-like) |
| $env/dynamic/public | Public (PUBLIC_*) | Runtime |
| $env/static/private | Server only | Build time (inlined) |
| $env/static/public | Public | Build time (inlined) |
Explicit env vars (opt-in, default in v3):
Enable in svelte.config.js: kit.experimental.explicitEnvironmentVariables = true. Then create src/env.ts:
import { defineEnvVars } from '@sveltejs/kit/hooks';
import * as v from 'valibot';
import { building } from '$app/env';
export const variables = defineEnvVars({
API_KEY: {}, // private
GOOGLE_ANALYTICS_ID: { public: true }, // public
SHOW_DEBUG_OVERLAY: { public: true, static: true }, // inlined, dead-code-eliminated
SECRET: { schema: building ? v.optional(v.string()) : v.string() },
CACHE_TTL_SECONDS: { description: 'How long...' }
});
Import from $app/env/private or $app/env/public. $app/environment is renamed to $app/env.
Three optional files: src/hooks.server.js, src/hooks.client.js, src/hooks.js.
Server (hooks.server.js):
handle({ event, resolve }) — runs on every request; return Response or call resolve(event, opts). resolve opts: transformPageChunk, filterSerializedResponseHeaders, preload. Use sequence(...) for multiple.handleFetch({ request, fetch, event }) — rewrite cross-origin requests to internal APIs; cookie forwarding for sibling subdomains.handleValidationError({ event, issues }) — customize 400 response for bad remote function args.Shared (hooks.server.js AND hooks.client.js):
handleError({ error, event, status, message }) — for unexpected errors only. Return { message, ... } -> becomes page.error. Must never throw. Server type: HandleServerError; client type: HandleClientError; client event is NavigationEvent.init() — runs once at startup; useful for DB connections.Universal (hooks.js):
reroute({ url, fetch }) — translate URL to a different route (e.g., i18n). Pure/idempotent. Can be async since 2.18.transport — custom encoders/decoders for types crossing the server/client boundary (e.g., Vector).error(404, 'Not found') (or error(404, { message, code })) from @sveltejs/kit. Renders nearest +error.svelte, sets status code. page.error = the object passed.Internal Error); routed through handleError.experimental.handleRenderingErrors. Error passed directly to +error.svelte as error prop (not via page.error).src/error.html with %sveltekit.status% and %sveltekit.error.message%. Errors in root +layout.server.js use fallback page (root contains +error.svelte).App.Error interface in src/app.d.ts:declare global {
namespace App {
interface Error { message: string; code: string; id: string; }
}
}
data-sveltekit-* attributes on <a> (or parent). Also apply to <form method="GET">.
| Attribute | Values | Effect |
|---|---|---|
| preload-data | hover (default), tap | Preload load data on hover/tap |
| preload-code | eager, viewport, hover, tap | Preload route code only |
| reload | boolean | Force full-page nav (also rel="external") |
| replacestate | boolean | Replace history entry instead of push |
| keepfocus | boolean | Keep focus on the same element after nav |
| noscroll | boolean | Disable scroll-to-top after nav |
Disable in subtree with data-sveltekit-preload-data="false". Respects navigator.connection.saveData.
Place src/service-worker.js (or src/service-worker/index.js) — bundled and auto-registered. Disable via config to register manually.
Available from $service-worker: base, build, files, prerendered, version.
Standard pattern: cache build + files on install, network-first with cache fallback on fetch. Skip responses with Cache-Control: no-store (live queries). build/prerendered are empty arrays in dev.
$env/static/private and $env/dynamic/private — server-only.$app/server — server-only..server.js suffix OR place under $lib/server/.Illegal imports from browser code error at build. Use import type for type-only. Detection is disabled in tests (process.env.TEST === 'true').
Snapshots — export snapshot = { capture, restore } from +page.svelte or +layout.svelte. Captured to sessionStorage before page updates; restored on history nav. Must be JSON-serializable. Don't return huge objects.
Shallow routing — pushState(url, state) / replaceState(url, state) create history entries without navigating. Read via page.state. Use preloadData(href) to grab load data, then pushState(href, { selected: result.data }) to render another +page.svelte inside a modal. page.state is always {} on first SSR and during first paint.
| Symptom | Fix |
|---|---|
| User data leaks between requests | Don't use module-level vars; use cookies + DB |
| load data not updating on nav | Use $derived(...) not const for values derived from data |
| Cannot import $lib/server/... | Move shared types to import type |
| Live query stream stops | Reconnect with .reconnect(); don't cache no-store responses in SW |
| error(404, ...) shows blank page | Add +error.svelte nearest to the route |
| Server-only env leaked to client | Use $env/static/private or .server naming |
| Validation 400 generic message | Implement handleValidationError |
| Component state lost on nav | Wrap with {#key page.url.pathname}<X/>{/key} to force remount |
load must be pure — no global store writes. Return the data instead.query.batch returns a single function that maps individual inputs to outputs; not a direct array of results.query.live on SSR returns ONLY the first yielded value then closes.command cannot be called during render — invoke from event handlers.requested requires a limit — DoS protection; pass Infinity only if explicitly safe.error() no longer needs throw in SvelteKit 2.x.page.state is {} on SSR and first paint — don't rely on it for critical render.reroute must be pure/idempotent — its result is cached per URL on the client.build/prerendered empty in dev — test in production build.handleFetch — SvelteKit can't tell which parent-domain cookie belongs to which subdomain.handleError must not throw — wrap risky work in try/catch.Q: $app/state vs $app/stores?
A: $app/state (since 2.12) is rune-based and reactive. $app/stores is the legacy store-based version. Use $app/state with Svelte 5.
Q: query vs prerender?
A: query for dynamic data; prerender for build-time-frozen data that can be served from a CDN. query cannot be used on a fully prerendered page.
Q: form vs command?
A: form is progressive-enhanced — works without JS, spreads onto <form>. command is JS-only, called from event handlers. Prefer form.
Q: $env/dynamic vs $env/static?
A: dynamic reads at runtime (e.g., process.env); static inlined at build time (enables dead-code elimination). Use static for build-time-known values.
Q: $env/static/private vs $env/dynamic/private?
A: Same access restriction; difference is when the value is read. Static gives DCE; dynamic allows runtime override (e.g., MY_FLAG=1 npm run dev).
Q: Can I use $lib/server modules from +page.svelte? A: No — server-only modules cannot be imported by client code, transitively. SvelteKit errors at build.
Q: How do I keep focus on a search input after submit?
A: Add data-sveltekit-keepfocus to the <form>.
Q: How do I throw a 404 from a load?
A: import { error } from '@sveltejs/kit'; error(404, 'Not found'); (don't throw in SvelteKit 2).
Q: Should I use Sentry?
A: Yes — initialize in handleError of both hooks.server.js and hooks.client.js. Server uses HandleServerError; client uses HandleClientError with NavigationEvent.
| Topic | File |
|---|---|
| State management with context + URL + derived | examples/state-management.md |
| Remote query/form/command/batch/live | examples/remote-functions.md |
| Env vars (legacy + explicit) | examples/env-vars.md |
| Server + universal hooks | examples/hooks.md |
| Expected/unexpected errors + App.Error | examples/errors.md |
| data-sveltekit-* link options | examples/link-options.md |
| Service worker precache + offline | examples/service-worker.md |
| $app/* modules (forms, navigation, state, paths, server) | examples/app-modules.md |
| Snapshots + shallow routing | examples/shallow-snapshots.md |
| Topic | File |
|---|---|
| State management | references/state-management.md |
| Remote functions | references/remote-functions.md |
| Environment variables | references/env-vars.md |
| Hooks | references/hooks.md |
| Errors | references/errors.md |
| Link options | references/link-options.md |
| Service workers | references/service-worker.md |
| Server-only modules | references/server-only.md |
| $app/* modules | references/app-modules.md |
| Snapshots & shallow routing | references/shallow-snapshots.md |