agentleFS
nledfordnledford/engineering-review-board/skills/svelte-sveltekit-engineering/SKILL.md

svelte-sveltekit-engineering

Skill11 starsChanged 11 days agoMITView on GitHub

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.

The file

Show its rules and text

We split this skill into the rules it gives an agent

23 rules. About 0 are common, 4 are distinctive, and 19 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 19

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill for Svelte component design, Svelte 5 runes/reactivity, and SvelteKit application structure, routing, data loading, and form …

    line 8
  2. Very specific:

    Use standalone Svelte (via Vite's svelte template, a component library, or embedding Svelte components into an existing non-Svelte page) …

    line 29
  3. Distinctive:

    Use SvelteKit when any of these apply:

    line 45
  4. Distinctive:

    If a project starts as standalone Svelte and later needs routing, SSR, or a backend-for-frontend layer, migrating to SvelteKit is …

    line 61
  5. Very specific:

    Use the context API (setContext/getContext) to pass state down a component tree without prop-drilling through intermediate components that …

    line 99
  6. Very specific:

    Use $derived — it is pull-based and avoids ordering bugs and extra renders.

    line 114
  7. Very specific:

    Keep components focused on presentation and local interaction.

    line 128
  8. Very specific:

    Prefer composition (slots/snippets, {#snippet}/{@render} in Svelte 5, or child components) over large components with many boolean props …

    line 132
  9. Very specific:

    Keep two-way binding (bind:) to genuine form-control-like components.

    line 139
  10. Very specific:

    Put shared, non-route code under src/lib (aliased as $lib) — components, utilities, stores/runes modules, server-only helpers.

    line 162
  11. Very specific:

    Keep hooks.server.js/hooks.client.js for cross-cutting concerns: authentication population (event.locals), request logging, error …

    line 170
  12. Very specific:

    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 …

    line 174
  13. Very specific:

    Keep a clear module boundary between UI components ($lib/components), domain/service logic ($lib/server/* or $lib/domain/*), and route glue …

    line 177
  14. Very specific:

    Use +page.server.js/+server.js for anything that must stay server-only.

    line 203
  15. Very specific:

    Prefer load functions and streaming (export const load = async () => ({ streamed: { slow: slowPromise() } })) over client-side spinners for …

    line 231
  16. Very specific:

    Use data-sveltekit-preload-data (on by default for hover) deliberately.

    line 234
  17. Very specific:

    Prefer prerendering (export const prerender = true) for content that is the same for every visitor and does not depend on cookies/session — …

    line 237
  18. Very specific:

    Avoid importing large libraries into universal code that also ships to the client.

    line 240
  19. Very specific:

    Use $derived instead of recomputing expensive values in the template or in an $effect on every dependency change.

    line 243
  20. Very specific:

    Use error(status, message) from @sveltejs/kit in load functions and actions for expected failures (404, 403, validation) so SvelteKit …

    line 251
  21. Very specific:

    Test load functions and form actions directly as functions (they are plain exported functions) with a constructed event-like object, rather …

    line 274
  22. Very specific:

    Use playwright-e2e for checked-in end-to-end coverage of navigation, form submission with progressive enhancement, authentication flows, …

    line 277
  23. Distinctive:

    No per-request mutable state lives in module scope on the server.

    line 331

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 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.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.