Svelte and SvelteKit Engineering
Use this skill for Svelte component design, Svelte 5 runes/reactivity, and
SvelteKit application structure, routing, data loading, and form handling.
Inspect the repository before assuming a version: check package.json for
svelte/@sveltejs/kit major versions, svelte.config.js for the adapter, and
whether components use runes ($state, $derived, $effect, $props) or
legacy Svelte 4 syntax (export let, $:, writable stores as the default
reactivity model). Do not silently rewrite Svelte 4 idioms to runes, or vice
versa, unless the task is an explicit migration.
Load javascript-typescript-engineering
for package manager workflow, TypeScript configuration, bundler/Vite mechanics,
and generic JS/TS lint/build/test commands. Load
css-scss-styling for <style> block
architecture, Tailwind, CSS modules, and design tokens. This skill owns
Svelte-specific and SvelteKit-specific decisions only.
Svelte (Standalone) vs. SvelteKit
Choose deliberately; do not default to SvelteKit for every UI, and do not reach
for standalone Svelte once routing or server behavior is needed.
Use standalone Svelte (via Vite's svelte template, a component library, or
embedding Svelte components into an existing non-Svelte page) when:
- The project is a single component, a widget embedded in another site or CMS,
a design-system/component-library package, or a browser extension UI.
- There is no need for file-based routing, multiple pages, or navigation state
owned by the framework.
- Rendering is client-only and SEO, first-paint content, or crawlability do not
matter — e.g., an internal dashboard behind auth, an admin widget, or a
<canvas>/data-viz component.
- The app has no server-side concerns of its own: no need to keep secrets off
the client, call a database directly, or render personalized HTML before
JavaScript runs.
- Deployment target is a static asset host or the component ships inside
another application's bundle (npm package, Web Component wrapper).
Use SvelteKit when any of these apply:
- The app needs more than one route, nested layouts, or URL-driven navigation.
- Pages need SSR or prerendering for SEO, social-preview metadata, or fast
perceived load on slow connections/devices.
- The app must keep secrets (API keys, database credentials) off the client —
+page.server.js/+server.js run only on the server.
- The app needs form handling with progressive enhancement, cookie-based
sessions, or server-validated mutations (form actions).
- Deployment targets a Node server, edge runtime (Cloudflare Workers, Vercel
Edge, Deno Deploy), or a serverless platform — SvelteKit adapters exist for
these; plain Svelte does not manage server output.
- The app benefits from SvelteKit's file-based conventions (
+page.svelte,
+layout.svelte, +server.js) to avoid hand-rolling a router, data-loading
convention, or error-boundary pattern.
If a project starts as standalone Svelte and later needs routing, SSR, or a
backend-for-frontend layer, migrating to SvelteKit is straightforward because
SvelteKit is built on the same component model — prefer that migration over
hand-building routing/SSR on top of plain Svelte.
Reactivity: Runes vs. Legacy Reactivity
Svelte 5 introduced runes as the default reactivity primitive. Match the
repository's actual syntax; a mixed codebase mid-migration is normal but new
code should follow the direction the repo has already chosen.
$state replaces implicit top-level reactive let bindings. Use it for
any local mutable value a component template reads. $state is deeply
reactive for plain objects/arrays; mutating a property (obj.x = 1,
arr.push(...)) triggers updates without needing to reassign.
$derived (and $derived.by(() => ...) for multi-statement logic)
replaces $: reactive declarations for computed values. Prefer $derived
over $effect whenever a value is computed from other state — $effect is
for side effects, not for producing values other code reads.
$effect replaces $: used for side effects (DOM APIs, subscriptions,
logging, syncing to localStorage). Keep effects narrow and clean up
subscriptions/timers/listeners by returning a teardown function. Avoid
$effect for state derivation — an effect that only sets another $state
from its dependencies is a smell; use $derived instead.
$props replaces export let for declaring component inputs, including
defaults (let { count = 0 } = $props()) and rest props
(let { class: className, ...rest } = $props()).
$bindable marks a prop that a parent may bind to with bind:; only mark
props bindable when two-way binding is a deliberate part of the component's
contract, not by default.
- Stores (
writable, readable, derived from svelte/store) are still
useful for state that must be shared across component instances without
prop-drilling, state that outlives a single component tree (app-wide
settings, auth session), or state consumed outside components (e.g., in a
.ts module). Runes replace stores for component-local reactive state; they
do not replace the store contract for cross-cutting shared state, though
$state in a plain .svelte.js/.svelte.ts module is now a common
runes-based alternative to a store for shared state.
- Use the context API (
setContext/getContext) to pass state down a
component tree without prop-drilling through intermediate components that
don't use it themselves — typically paired with a $state object so
descendants get reactive updates. Context is not a global store: it is scoped
to the component tree rooted where setContext was called, which makes it
safe for per-request/per-instance state (important in SSR, where a
module-level singleton would leak state between requests).
Reactivity Anti-Patterns
- Module-level mutable
$state/store used to hold per-request or per-user data
in a SvelteKit app. On the server, module scope is shared across concurrent
requests — this leaks one user's data into another's response. Use load
return values, event.locals, or context populated per-request instead.
- Using
$effect to derive a value that other code reads ($effect(() => count2 = count * 2)).
Use $derived — it is pull-based and avoids ordering bugs and extra renders.
- Deep-cloning or reassigning entire objects/arrays to force reactivity. With
$state, mutation is already tracked; reassignment gymnastics left over from
Svelte 4's array-reassignment workaround are unnecessary and hurt
readability.
- Subscribing to a store manually with
.subscribe() inside a component instead
of using the $store auto-subscription syntax, causing manual unsubscribe
bugs and memory leaks in components without a matching cleanup.
- Putting business logic inside
$effect blocks that reach into the DOM
directly when a declarative binding (bind:value, class:, style:) would
do the same thing more predictably.
Component Design
- Keep components focused on presentation and local interaction; push data
fetching, validation, and cross-cutting business rules to
load functions,
form actions, or plain .ts modules that components import. A component that
both fetches data and renders it is harder to test and reuse.
- Prefer composition (slots/snippets,
{#snippet}/{@render} in Svelte 5, or
child components) over large components with many boolean props that toggle
internal branches. A variant/boolean-prop explosion is a sign the component
should split or use composition instead.
- Type props explicitly in TypeScript projects (
let { items }: { items: Item[] } = $props();)
rather than inferring from usage; this keeps the component's contract visible
at a glance and catches mismatches at the call site.
- Keep two-way binding (
bind:) to genuine form-control-like components. Avoid
exposing bind: on every prop by default — most parent/child communication
should flow one-directionally down via props and up via callback props or
events, reserving $bindable for controls that behave like native form
elements.
- Name event-handling props consistently (
onclick, onsubmit, or a domain
name like onSelect) since Svelte 5 uses plain props for event handlers
rather than createEventDispatcher in new code; reserve
createEventDispatcher for maintaining existing Svelte 4 component
contracts.
- Co-locate a component's styles in its own
<style> block; Svelte scopes
styles per component automatically, so avoid hand-rolled class-name
namespacing to prevent collisions. Route non-trivial styling architecture
decisions to css-scss-styling.
Project Structure and File Conventions
- Respect SvelteKit's routing conventions in
src/routes: +page.svelte for a
page, +layout.svelte for shared layout, +page.server.js/.ts and
+layout.server.js for server-only data and actions, +server.js for
standalone API endpoints, and +error.svelte for route-scoped error UI.
Route groups ((group)) organize routes without affecting the URL; matcher
files (src/params/*.js) validate dynamic segment shape.
- Put shared, non-route code under
src/lib (aliased as $lib) — components,
utilities, stores/runes modules, server-only helpers. Use $lib/server (or an
equivalent convention) for modules that must never be imported from
client-rendered code, and treat any import of a server-only module from a
+page.svelte as a bug: SvelteKit only enforces this boundary for code that
actually reaches +page.server.js/+server.js/hooks.server.js — a
client-imported module has no such protection, so keep secrets and
privileged logic out of anything reachable from the client bundle.
- Keep
hooks.server.js/hooks.client.js for cross-cutting concerns:
authentication population (event.locals), request logging, error
normalization. Avoid stuffing route-specific logic into hooks — that belongs
in the route's own load/action/server endpoint.
- Use
app.d.ts to type App.Locals, App.PageData, App.Error, and
App.Platform so event.locals and load return values are typed
end-to-end instead of any.
- Keep a clear module boundary between UI components (
$lib/components),
domain/service logic ($lib/server/* or $lib/domain/*), and route glue
(+page.server.ts, +server.ts). Route files should stay thin — parse
input, call a service function, shape the response — mirroring the
thin-handler guidance in
hexagonal-architecture when the
backend logic is substantial enough to warrant explicit ports/adapters.
Design Patterns
Load the Svelte/SvelteKit patterns reference for
worked guidance on layout composition, load function design (universal vs.
server, streaming/parallel loading, invalidation), form actions and
progressive enhancement, and other patterns that appear repeatedly in
production SvelteKit apps.
Anti-Patterns
Load the Svelte/SvelteKit anti-patterns reference
for a fuller catalogue. Highest-impact items:
- Fetching data in
onMount/$effect inside a page component instead of a
load function — this delays rendering, breaks SSR, and duplicates
loading/error-state handling that SvelteKit already provides.
- Calling a database, ORM, or secret-bearing API directly from a
.svelte
component or a universal load (+page.js) that also runs in the browser.
Use +page.server.js/+server.js for anything that must stay server-only.
- Storing all application state in one global store, turning it into an
untyped, unscoped god-object. Scope state to the route or component tree that
owns it; use context or route-level
load data instead.
- Reimplementing form submission with
fetch and manual preventDefault when
a form action plus use:enhance would give the same UX with progressive
enhancement and less code.
- Ignoring
+error.svelte and letting every failure fall through to a generic
crash page, instead of throwing error()/redirect() from load or actions
with actionable status codes and messages.
Backend Integration
Load the backend integration reference
when a SvelteKit frontend talks to a separate backend service (Rust/Axum, PHP,
Python/FastAPI or Django, or another API) rather than owning all server logic
itself. It covers API communication patterns, authentication handoff (cookies
vs. bearer tokens, BFF vs. direct-to-API), and deployment topology choices.
Route the backend's own implementation to its owning skill:
rust-async-web for Axum,
php-engineering for PHP, and
python-engineering for FastAPI/Django. Use
api-design when the contract itself (resource
shape, error envelope, versioning) is still being decided. This skill owns only
how the SvelteKit side consumes and is deployed alongside that backend.
Performance
- Prefer
load functions and streaming (export const load = async () => ({ streamed: { slow: slowPromise() } }))
over client-side spinners for expensive data — SvelteKit can render the
shell immediately and stream slow data in.
- Use
data-sveltekit-preload-data (on by default for hover) deliberately;
disable preloading for expensive or side-effecting links rather than leaving
it default everywhere.
- Prefer prerendering (
export const prerender = true) for content that is the
same for every visitor and does not depend on cookies/session — it removes
server round-trips entirely for that route.
- Avoid importing large libraries into universal code that also ships to the
client; keep heavy parsing/formatting/crypto libraries in
+page.server.ts
when the result, not the library, is what the client needs.
- Use
$derived instead of recomputing expensive values in the template or in
an $effect on every dependency change; $derived values are cached until a
dependency actually changes.
- Route deeper profiling, bundle-budget, or Core Web Vitals work to
performance-review.
Error Handling
- Use
error(status, message) from @sveltejs/kit in load functions and
actions for expected failures (404, 403, validation) so SvelteKit renders the
nearest +error.svelte with the right status code.
- Return
fail(status, data) from form actions for validation errors so the
form re-renders with the submitted values and field errors, instead of
throwing and losing form state.
- Implement
handleError in hooks.server.js/hooks.client.js to log
unexpected exceptions with a correlation id and return a safe, user-facing
message — never leak stack traces or internal error details to the client in
production. Route structured logging/correlation-id design to
observability-engineering.
- Distinguish expected domain errors (validation, not-found, unauthorized) from
unexpected exceptions (bugs, network failures) — only the former should
produce a specific status code and message; let the latter hit
handleError.
Testing
- Unit-test pure logic (utilities, derived-state functions, form-validation
schemas) with the repository's configured runner (commonly Vitest) without
mounting components.
- Component-test with
@testing-library/svelte (or the repo's established
tool) for rendering behavior, prop/event contracts, and accessibility roles —
test what the user sees and does, not internal component state.
- Test
load functions and form actions directly as functions (they are plain
exported functions) with a constructed event-like object, rather than only
through full page renders.
- Use
playwright-e2e for checked-in end-to-end
coverage of navigation, form submission with progressive enhancement,
authentication flows, and hydration-sensitive behavior that only a real
browser can verify. Load testing-strategy
for deciding the overall unit/component/E2E balance.
Accessibility
Svelte's compiler emits accessibility warnings (missing alt, invalid ARIA
roles, non-interactive elements with click handlers) — treat these as build
warnings to fix, not noise to suppress with <!-- svelte-ignore --> unless the
suppression is justified and commented. Load
ux-accessibility-review for a full
accessibility audit of interactive components, forms, and focus management
(especially around client-side navigation, where SvelteKit does not
automatically move focus or announce route changes the way a full page load
would).
Security Review Prompts
Load security-review when SvelteKit work
touches authentication, session cookies, CSRF, form actions that mutate state,
redirects, file uploads, +server.js endpoints exposed to the internet, or
data rendered with {@html ...} (a raw HTML injection point). Use
threat-modeling before or during new
hooks.server.js auth logic, new +server.js endpoints, or a new
backend-for-frontend boundary. Use
dependency-supply-chain-review
for svelte/@sveltejs/kit/adapter/plugin version bumps and new dependencies.
Deployment and Adapters
- Match the SvelteKit adapter to the actual deployment target:
@sveltejs/adapter-node for a long-running Node server,
@sveltejs/adapter-static for a fully prerendered static site,
@sveltejs/adapter-vercel/adapter-netlify/adapter-cloudflare for those
platforms, and community adapters for other targets. Do not assume Vercel by
default — check svelte.config.js.
- Static/prerendered output has no server runtime:
+page.server.js,
+server.js, and form actions requiring a live server will not work under
adapter-static unless the route is explicitly excluded from prerendering
and the platform still runs a server.
- Route Dockerfile/Compose packaging to
container-engineering and hosted CI/CD
pipeline configuration to
ci-release-engineering; keep adapter
selection and environment-variable/platform-binding mechanics here.
Review Checklist
- Standalone Svelte vs. SvelteKit was a deliberate choice based on routing,
SSR, secret-handling, and deployment needs — not a default.
- Reactivity uses
$state/$derived/$effect/$props correctly in Svelte 5
code, with no $effect used purely to derive a value.
- No per-request mutable state lives in module scope on the server.
- Data fetching and secret-bearing calls happen in
load/+server.js/actions,
never directly in .svelte components or client-reachable code.
- Route files stay thin; domain/service logic lives in
$lib.
- Forms use actions plus
use:enhance where progressive enhancement matters;
validation errors return via fail, not thrown exceptions.
- Errors distinguish expected (
error()/fail()) from unexpected
(handleError) cases, and no internal details leak to the client.
- The adapter matches the real deployment target, and prerendered routes don't
depend on per-request server behavior.
- Accessibility warnings from the Svelte compiler are addressed, not
suppressed, and route-change focus/announcement is considered.
- Tests exist at the right layer: unit for logic, component tests for
rendering/interaction, Playwright E2E for hydration- and navigation-sensitive
flows.
Zod SvelteKit Routing
For selected Zod schemas, load zod-engineering. Parse in server loads/actions/endpoints where appropriate, keep server-only code out of client bundles, validate runtime responses, and map expected failures to supported fail/error shapes.
1---2name: svelte-sveltekit-engineering3description: Svelte and SvelteKit engineering guidance. Use when adding, changing, reviewing, or testing .svelte components, Svelte 5 runes, stores, context, SvelteKit routing, load functions, form actions, hooks, adapters, or deciding between standalone Svelte and full SvelteKit. Use javascript-typescript-engineering for generic JS/TS package/build/test mechanics, css-scss-styling for stylesheet and Tailwind decisions, api-design for backend contract shape, and ux-accessibility-review for accessibility audits. Do not use for non-Svelte frontend frameworks.4---56# Svelte and SvelteKit Engineering78Use this skill for Svelte component design, Svelte 5 runes/reactivity, and9SvelteKit application structure, routing, data loading, and form handling.10Inspect the repository before assuming a version: check `package.json` for11`svelte`/`@sveltejs/kit` major versions, `svelte.config.js` for the adapter, and12whether components use runes (`$state`, `$derived`, `$effect`, `$props`) or13legacy Svelte 4 syntax (`export let`, `$:`, `writable` stores as the default14reactivity model). Do not silently rewrite Svelte 4 idioms to runes, or vice15versa, unless the task is an explicit migration.1617Load [`javascript-typescript-engineering`](../javascript-typescript-engineering/SKILL.md)18for package manager workflow, TypeScript configuration, bundler/Vite mechanics,19and generic JS/TS lint/build/test commands. Load20[`css-scss-styling`](../css-scss-styling/SKILL.md) for `<style>` block21architecture, Tailwind, CSS modules, and design tokens. This skill owns22Svelte-specific and SvelteKit-specific decisions only.2324## Svelte (Standalone) vs. SvelteKit2526Choose deliberately; do not default to SvelteKit for every UI, and do not reach27for standalone Svelte once routing or server behavior is needed.2829Use **standalone Svelte** (via Vite's `svelte` template, a component library, or30embedding Svelte components into an existing non-Svelte page) when:3132- The project is a single component, a widget embedded in another site or CMS,33 a design-system/component-library package, or a browser extension UI.34- There is no need for file-based routing, multiple pages, or navigation state35 owned by the framework.36- Rendering is client-only and SEO, first-paint content, or crawlability do not37 matter — e.g., an internal dashboard behind auth, an admin widget, or a38 `<canvas>`/data-viz component.39- The app has no server-side concerns of its own: no need to keep secrets off40 the client, call a database directly, or render personalized HTML before41 JavaScript runs.42- Deployment target is a static asset host or the component ships inside43 another application's bundle (npm package, Web Component wrapper).4445Use **SvelteKit** when any of these apply:4647- The app needs more than one route, nested layouts, or URL-driven navigation.48- Pages need SSR or prerendering for SEO, social-preview metadata, or fast49 perceived load on slow connections/devices.50- The app must keep secrets (API keys, database credentials) off the client —51 `+page.server.js`/`+server.js` run only on the server.52- The app needs form handling with progressive enhancement, cookie-based53 sessions, or server-validated mutations (form actions).54- Deployment targets a Node server, edge runtime (Cloudflare Workers, Vercel55 Edge, Deno Deploy), or a serverless platform — SvelteKit adapters exist for56 these; plain Svelte does not manage server output.57- The app benefits from SvelteKit's file-based conventions (`+page.svelte`,58 `+layout.svelte`, `+server.js`) to avoid hand-rolling a router, data-loading59 convention, or error-boundary pattern.6061If a project starts as standalone Svelte and later needs routing, SSR, or a62backend-for-frontend layer, migrating to SvelteKit is straightforward because63SvelteKit is built on the same component model — prefer that migration over64hand-building routing/SSR on top of plain Svelte.6566## Reactivity: Runes vs. Legacy Reactivity6768Svelte 5 introduced runes as the default reactivity primitive. Match the69repository's actual syntax; a mixed codebase mid-migration is normal but new70code should follow the direction the repo has already chosen.7172- **`$state`** replaces implicit top-level reactive `let` bindings. Use it for73 any local mutable value a component template reads. `$state` is deeply74 reactive for plain objects/arrays; mutating a property (`obj.x = 1`,75 `arr.push(...)`) triggers updates without needing to reassign.76- **`$derived`** (and `$derived.by(() => ...)` for multi-statement logic)77 replaces `$:` reactive declarations for computed values. Prefer `$derived`78 over `$effect` whenever a value is *computed from* other state — `$effect` is79 for side effects, not for producing values other code reads.80- **`$effect`** replaces `$:` used for side effects (DOM APIs, subscriptions,81 logging, syncing to `localStorage`). Keep effects narrow and clean up82 subscriptions/timers/listeners by returning a teardown function. Avoid83 `$effect` for state derivation — an effect that only sets another `$state`84 from its dependencies is a smell; use `$derived` instead.85- **`$props`** replaces `export let` for declaring component inputs, including86 defaults (`let { count = 0 } = $props()`) and rest props87 (`let { class: className, ...rest } = $props()`).88- **`$bindable`** marks a prop that a parent may bind to with `bind:`; only mark89 props bindable when two-way binding is a deliberate part of the component's90 contract, not by default.91- Stores (`writable`, `readable`, `derived` from `svelte/store`) are still92 useful for state that must be shared across component instances without93 prop-drilling, state that outlives a single component tree (app-wide94 settings, auth session), or state consumed outside components (e.g., in a95 `.ts` module). Runes replace stores for component-local reactive state; they96 do not replace the store contract for cross-cutting shared state, though97 `$state` in a plain `.svelte.js`/`.svelte.ts` module is now a common98 runes-based alternative to a store for shared state.99- Use the **context API** (`setContext`/`getContext`) to pass state down a100 component tree without prop-drilling through intermediate components that101 don't use it themselves — typically paired with a `$state` object so102 descendants get reactive updates. Context is not a global store: it is scoped103 to the component tree rooted where `setContext` was called, which makes it104 safe for per-request/per-instance state (important in SSR, where a105 module-level singleton would leak state between requests).106107### Reactivity Anti-Patterns108109- Module-level mutable `$state`/store used to hold per-request or per-user data110 in a SvelteKit app. On the server, module scope is shared across concurrent111 requests — this leaks one user's data into another's response. Use `load`112 return values, `event.locals`, or context populated per-request instead.113- Using `$effect` to derive a value that other code reads (`$effect(() => count2 = count * 2)`).114 Use `$derived` — it is pull-based and avoids ordering bugs and extra renders.115- Deep-cloning or reassigning entire objects/arrays to force reactivity. With116 `$state`, mutation is already tracked; reassignment gymnastics left over from117 Svelte 4's array-reassignment workaround are unnecessary and hurt118 readability.119- Subscribing to a store manually with `.subscribe()` inside a component instead120 of using the `$store` auto-subscription syntax, causing manual unsubscribe121 bugs and memory leaks in components without a matching cleanup.122- Putting business logic inside `$effect` blocks that reach into the DOM123 directly when a declarative binding (`bind:value`, `class:`, `style:`) would124 do the same thing more predictably.125126## Component Design127128- Keep components focused on presentation and local interaction; push data129 fetching, validation, and cross-cutting business rules to `load` functions,130 form actions, or plain `.ts` modules that components import. A component that131 both fetches data and renders it is harder to test and reuse.132- Prefer composition (slots/snippets, `{#snippet}`/`{@render}` in Svelte 5, or133 child components) over large components with many boolean props that toggle134 internal branches. A `variant`/boolean-prop explosion is a sign the component135 should split or use composition instead.136- Type props explicitly in TypeScript projects (`let { items }: { items: Item[] } = $props();`)137 rather than inferring from usage; this keeps the component's contract visible138 at a glance and catches mismatches at the call site.139- Keep two-way binding (`bind:`) to genuine form-control-like components. Avoid140 exposing `bind:` on every prop by default — most parent/child communication141 should flow one-directionally down via props and up via callback props or142 events, reserving `$bindable` for controls that behave like native form143 elements.144- Name event-handling props consistently (`onclick`, `onsubmit`, or a domain145 name like `onSelect`) since Svelte 5 uses plain props for event handlers146 rather than `createEventDispatcher` in new code; reserve147 `createEventDispatcher` for maintaining existing Svelte 4 component148 contracts.149- Co-locate a component's styles in its own `<style>` block; Svelte scopes150 styles per component automatically, so avoid hand-rolled class-name151 namespacing to prevent collisions. Route non-trivial styling architecture152 decisions to [`css-scss-styling`](../css-scss-styling/SKILL.md).153154## Project Structure and File Conventions155156- Respect SvelteKit's routing conventions in `src/routes`: `+page.svelte` for a157 page, `+layout.svelte` for shared layout, `+page.server.js`/`.ts` and158 `+layout.server.js` for server-only data and actions, `+server.js` for159 standalone API endpoints, and `+error.svelte` for route-scoped error UI.160 Route groups (`(group)`) organize routes without affecting the URL; matcher161 files (`src/params/*.js`) validate dynamic segment shape.162- Put shared, non-route code under `src/lib` (aliased as `$lib`) — components,163 utilities, stores/runes modules, server-only helpers. Use `$lib/server` (or an164 equivalent convention) for modules that must never be imported from165 client-rendered code, and treat any import of a server-only module from a166 `+page.svelte` as a bug: SvelteKit only enforces this boundary for code that167 actually reaches `+page.server.js`/`+server.js`/`hooks.server.js` — a168 client-imported module has no such protection, so keep secrets and169 privileged logic out of anything reachable from the client bundle.170- Keep `hooks.server.js`/`hooks.client.js` for cross-cutting concerns:171 authentication population (`event.locals`), request logging, error172 normalization. Avoid stuffing route-specific logic into hooks — that belongs173 in the route's own `load`/action/server endpoint.174- Use `app.d.ts` to type `App.Locals`, `App.PageData`, `App.Error`, and175 `App.Platform` so `event.locals` and `load` return values are typed176 end-to-end instead of `any`.177- Keep a clear module boundary between UI components (`$lib/components`),178 domain/service logic (`$lib/server/*` or `$lib/domain/*`), and route glue179 (`+page.server.ts`, `+server.ts`). Route files should stay thin — parse180 input, call a service function, shape the response — mirroring the181 thin-handler guidance in182 [`hexagonal-architecture`](../hexagonal-architecture/SKILL.md) when the183 backend logic is substantial enough to warrant explicit ports/adapters.184185## Design Patterns186187Load the [Svelte/SvelteKit patterns reference](references/patterns.md) for188worked guidance on layout composition, `load` function design (universal vs.189server, streaming/parallel loading, invalidation), form actions and190progressive enhancement, and other patterns that appear repeatedly in191production SvelteKit apps.192193## Anti-Patterns194195Load the [Svelte/SvelteKit anti-patterns reference](references/anti-patterns.md)196for a fuller catalogue. Highest-impact items:197198- Fetching data in `onMount`/`$effect` inside a page component instead of a199 `load` function — this delays rendering, breaks SSR, and duplicates200 loading/error-state handling that SvelteKit already provides.201- Calling a database, ORM, or secret-bearing API directly from a `.svelte`202 component or a universal `load` (`+page.js`) that also runs in the browser.203 Use `+page.server.js`/`+server.js` for anything that must stay server-only.204- Storing all application state in one global store, turning it into an205 untyped, unscoped god-object. Scope state to the route or component tree that206 owns it; use context or route-level `load` data instead.207- Reimplementing form submission with `fetch` and manual `preventDefault` when208 a form action plus `use:enhance` would give the same UX with progressive209 enhancement and less code.210- Ignoring `+error.svelte` and letting every failure fall through to a generic211 crash page, instead of throwing `error()`/`redirect()` from `load` or actions212 with actionable status codes and messages.213214## Backend Integration215216Load the [backend integration reference](references/backend-integration.md)217when a SvelteKit frontend talks to a separate backend service (Rust/Axum, PHP,218Python/FastAPI or Django, or another API) rather than owning all server logic219itself. It covers API communication patterns, authentication handoff (cookies220vs. bearer tokens, BFF vs. direct-to-API), and deployment topology choices.221222Route the backend's own implementation to its owning skill:223[`rust-async-web`](../rust-async-web/SKILL.md) for Axum,224[`php-engineering`](../php-engineering/SKILL.md) for PHP, and225[`python-engineering`](../python-engineering/SKILL.md) for FastAPI/Django. Use226[`api-design`](../api-design/SKILL.md) when the contract itself (resource227shape, error envelope, versioning) is still being decided. This skill owns only228how the SvelteKit side consumes and is deployed alongside that backend.229230## Performance231232- Prefer `load` functions and streaming (`export const load = async () => ({ streamed: { slow: slowPromise() } })`)233 over client-side spinners for expensive data — SvelteKit can render the234 shell immediately and stream slow data in.235- Use `data-sveltekit-preload-data` (on by default for `hover`) deliberately;236 disable preloading for expensive or side-effecting links rather than leaving237 it default everywhere.238- Prefer prerendering (`export const prerender = true`) for content that is the239 same for every visitor and does not depend on cookies/session — it removes240 server round-trips entirely for that route.241- Avoid importing large libraries into universal code that also ships to the242 client; keep heavy parsing/formatting/crypto libraries in `+page.server.ts`243 when the result, not the library, is what the client needs.244- Use `$derived` instead of recomputing expensive values in the template or in245 an `$effect` on every dependency change; `$derived` values are cached until a246 dependency actually changes.247- Route deeper profiling, bundle-budget, or Core Web Vitals work to248 [`performance-review`](../performance-review/SKILL.md).249250## Error Handling251252- Use `error(status, message)` from `@sveltejs/kit` in `load` functions and253 actions for expected failures (404, 403, validation) so SvelteKit renders the254 nearest `+error.svelte` with the right status code.255- Return `fail(status, data)` from form actions for validation errors so the256 form re-renders with the submitted values and field errors, instead of257 throwing and losing form state.258- Implement `handleError` in `hooks.server.js`/`hooks.client.js` to log259 unexpected exceptions with a correlation id and return a safe, user-facing260 message — never leak stack traces or internal error details to the client in261 production. Route structured logging/correlation-id design to262 [`observability-engineering`](../observability-engineering/SKILL.md).263- Distinguish expected domain errors (validation, not-found, unauthorized) from264 unexpected exceptions (bugs, network failures) — only the former should265 produce a specific status code and message; let the latter hit `handleError`.266267## Testing268269- Unit-test pure logic (utilities, derived-state functions, form-validation270 schemas) with the repository's configured runner (commonly Vitest) without271 mounting components.272- Component-test with `@testing-library/svelte` (or the repo's established273 tool) for rendering behavior, prop/event contracts, and accessibility roles —274 test what the user sees and does, not internal component state.275- Test `load` functions and form actions directly as functions (they are plain276 exported functions) with a constructed `event`-like object, rather than only277 through full page renders.278- Use [`playwright-e2e`](../playwright-e2e/SKILL.md) for checked-in end-to-end279 coverage of navigation, form submission with progressive enhancement,280 authentication flows, and hydration-sensitive behavior that only a real281 browser can verify. Load [`testing-strategy`](../testing-strategy/SKILL.md)282 for deciding the overall unit/component/E2E balance.283284## Accessibility285286Svelte's compiler emits accessibility warnings (missing `alt`, invalid ARIA287roles, non-interactive elements with click handlers) — treat these as build288warnings to fix, not noise to suppress with `<!-- svelte-ignore -->` unless the289suppression is justified and commented. Load290[`ux-accessibility-review`](../ux-accessibility-review/SKILL.md) for a full291accessibility audit of interactive components, forms, and focus management292(especially around client-side navigation, where SvelteKit does not293automatically move focus or announce route changes the way a full page load294would).295296## Security Review Prompts297298Load [`security-review`](../security-review/SKILL.md) when SvelteKit work299touches authentication, session cookies, CSRF, form actions that mutate state,300redirects, file uploads, `+server.js` endpoints exposed to the internet, or301data rendered with `{@html ...}` (a raw HTML injection point). Use302[`threat-modeling`](../threat-modeling/SKILL.md) before or during new303`hooks.server.js` auth logic, new `+server.js` endpoints, or a new304backend-for-frontend boundary. Use305[`dependency-supply-chain-review`](../dependency-supply-chain-review/SKILL.md)306for `svelte`/`@sveltejs/kit`/adapter/plugin version bumps and new dependencies.307308## Deployment and Adapters309310- Match the SvelteKit adapter to the actual deployment target:311 `@sveltejs/adapter-node` for a long-running Node server,312 `@sveltejs/adapter-static` for a fully prerendered static site,313 `@sveltejs/adapter-vercel`/`adapter-netlify`/`adapter-cloudflare` for those314 platforms, and community adapters for other targets. Do not assume Vercel by315 default — check `svelte.config.js`.316- Static/prerendered output has no server runtime: `+page.server.js`,317 `+server.js`, and form actions requiring a live server will not work under318 `adapter-static` unless the route is explicitly excluded from prerendering319 and the platform still runs a server.320- Route Dockerfile/Compose packaging to321 [`container-engineering`](../container-engineering/SKILL.md) and hosted CI/CD322 pipeline configuration to323 [`ci-release-engineering`](../ci-release-engineering/SKILL.md); keep adapter324 selection and environment-variable/platform-binding mechanics here.325326## Review Checklist327328- Standalone Svelte vs. SvelteKit was a deliberate choice based on routing,329 SSR, secret-handling, and deployment needs — not a default.330- Reactivity uses `$state`/`$derived`/`$effect`/`$props` correctly in Svelte 5331 code, with no `$effect` used purely to derive a value.332- No per-request mutable state lives in module scope on the server.333- Data fetching and secret-bearing calls happen in `load`/`+server.js`/actions,334 never directly in `.svelte` components or client-reachable code.335- Route files stay thin; domain/service logic lives in `$lib`.336- Forms use actions plus `use:enhance` where progressive enhancement matters;337 validation errors return via `fail`, not thrown exceptions.338- Errors distinguish expected (`error()`/`fail()`) from unexpected339 (`handleError`) cases, and no internal details leak to the client.340- The adapter matches the real deployment target, and prerendered routes don't341 depend on per-request server behavior.342- Accessibility warnings from the Svelte compiler are addressed, not343 suppressed, and route-change focus/announcement is considered.344- Tests exist at the right layer: unit for logic, component tests for345 rendering/interaction, Playwright E2E for hydration- and navigation-sensitive346 flows.347348## Zod SvelteKit Routing349350For selected Zod schemas, load [`zod-engineering`](../zod-engineering/SKILL.md). Parse in server loads/actions/endpoints where appropriate, keep server-only code out of client bundles, validate runtime responses, and map expected failures to supported `fail`/`error` shapes.