Claude Code Svelte SvelteKit CLAUDE.md Runes TypeScript 2026

Claude Code for Svelte 5 and SvelteKit: CLAUDE.md Rules for Runes, Remote Functions, and Form Actions (2026)

The Prompt Shelf ·

Svelte is one of the few mainstream frontend frameworks in our gallery’s language-stack series that still had zero dedicated Claude Code coverage — despite three separate rule sets already living in the gallery (SvelteKit + TypeScript, Svelte 5 migration, and an AGENTS.md example) and a training-data problem that’s genuinely unusual: Svelte 4’s $: reactive statements are still the majority pattern in public code, so Claude Code defaults to writing them even on a project that migrated to runes over a year ago.

The two existing CLAUDE.md write-ups we could find for Svelte cover the load-functions-and-form-actions layer competently, but neither one touches SvelteKit’s remote functions — the experimental data layer that’s had steady monthly releases through 2026 and is explicitly positioned as the eventual replacement for +page.server.ts load functions. We checked what’s live against SvelteKit 2.70 and the @next preview of SvelteKit 3, and built a template around the decisions that actually cause Claude Code to write code for the wrong version of the framework.

Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.

Why a Generic “Svelte Best Practices” List Isn’t Enough

Svelte’s compiler catches syntax errors, but it has no opinion on which era of the framework your codebase is targeting. Left without project-specific rules, Claude Code tends to:

  • Write $: doubled = count * 2 reactive declarations on a project that migrated to $derived(count * 2), because Svelte 4 syntax still dominates tutorials, Stack Overflow answers, and older open-source repos
  • Fetch data with onMount + fetch() inside a component instead of a +page.server.ts load function, missing SvelteKit’s SSR data flow entirely
  • Reach for a client-side API route (+server.ts called via fetch) for an internal mutation that a form action would handle with progressive enhancement built in
  • Mix export let prop (Svelte 4 props) with $props() (Svelte 5 runes) in the same component
  • Read process.env for a client-accessible value instead of $env/static/public, which either throws at build time or leaks a server-only secret depending on which one gets mixed up
  • Introduce a Svelte store (writable, derived from svelte/store) for state that a runes-based project would model with a .svelte.ts module and $state

None of this fails svelte-check reliably — mixed prop syntax and stale reactive statements often compile and render correctly in dev, and the drift only becomes obvious when two contributors’ components stop behaving consistently.

What’s Actually Current in Svelte and SvelteKit (2026)

A CLAUDE.md written when Svelte 5 first shipped (October 2024) is already missing a full year of SvelteKit’s data-layer evolution:

  • Runes have been stable since Svelte 5.0. $state, $derived, $effect, $props, and $bindable are the default reactivity model. The svelte package is past 5.56 as of August 2026, with steady point releases — none of them reintroduce the old $: syntax as the recommended pattern.
  • Remote functions are experimental but actively shipping. query, form, command, and prerender from $app/server let you call server-only code directly from components, with automatic caching, single-flight mutations, and type safety — without writing a +page.server.ts load function or a +server.ts endpoint by hand. They’re opt-in via svelte.config.js and not yet the default, but SvelteKit’s own docs describe them as “intended to become the recommended way to communicate with the server.”
  • SvelteKit 3 is in @next preview. Thirteen preview releases shipped in July 2026 alone, previewing new $app/manifest and $app/service-worker modules and several breaking changes: error(status, {...}) now requires a message, invalidateAll is deprecated in favor of refreshAll, and pushState/replaceState/noScroll/keepFocus collapse into a single state/reset option on goto. None of this is stable yet — pin your CLAUDE.md to whichever major version the project is actually running.
  • svelte-check and ESLint’s Svelte plugin catch prop and rune misuse, but only after the code is written — they don’t stop Claude Code from reaching for the wrong pattern in the first place.

If the project hasn’t opted into remote functions or SvelteKit 3, say so explicitly — the failure mode is Claude Code assuming the newest experimental API is already the project’s convention.

A CLAUDE.md Template for Svelte/SvelteKit Projects

# CLAUDE.md — Svelte/SvelteKit Project

## Stack
- Svelte {5.x}, SvelteKit {2.x / 3.x @next}
- Data layer: {load functions + form actions / remote functions (experimental)} — see "Data Layer" below
- Styling: {Tailwind / vanilla CSS / UnoCSS}
- Testing: Vitest (unit) + Playwright (e2e) — see our [Jest/Vitest/Playwright guide](/blog/claude-code-testing-claude-md-jest-vitest-playwright-2026)

## Reactivity Rules
- Runes only: `$state()`, `$derived()`, `$effect()`, `$props()`, `$bindable()`.
- Never write `$: x = ...` reactive statements or `export let prop` — this is Svelte 4 syntax and this project is on runes.
- Cross-component/shared state lives in a `.svelte.ts` module exporting `$state`-backed objects, not `svelte/store`'s `writable`/`derived`, unless the file already uses stores.
- Use `{#snippet}` for reusable markup fragments instead of duplicating template blocks.

## Data Layer
- {If load functions}: Server-only data fetching goes in `+page.server.ts`, never inside `onMount`. Universal data (needed on both server and client) goes in `+page.ts`.
- {If remote functions}: New reads use `query()` from `$app/server`; new internal mutations use `form()` or `command()`. Do not add a `+server.ts` endpoint for something a remote function already covers.
- Environment variables: `$env/static/public` for client-safe values, `$env/static/private` for server-only. Never `process.env` — it isn't populated the same way across adapters.

## Forms and Mutations
- {If form actions}: Use SvelteKit form actions (`export const actions = {...}`) with `use:enhance` for progressive enhancement, not a client-side `fetch()` to a custom endpoint.
- {If remote functions}: Use `form()` from `$app/server` with a Standard Schema validator (Valibot or Zod), bound via `<form {...myFormFunction}>`.
- Do not build a client-only form submission flow that bypasses SSR/progressive enhancement without an explicit reason noted in the PR.

## Commands
- Dev: `npm run dev`
- Build: `npm run build`
- Type check: `npm run check` (runs `svelte-check`)
- Test: `npm run test` (Vitest) / `npm run test:e2e` (Playwright)

The “Data Layer” section is the one most templates skip entirely, and it’s the one most likely to produce code that runs but targets the wrong SvelteKit era — a component fetching through onMount when the project has committed to load functions, or a new +server.ts endpoint added by hand when the project has already opted into remote functions for exactly that use case.

Remote Functions vs. Load Functions + Form Actions

Both are legitimate 2026 choices, and picking wrong mid-project produces two data-fetching patterns coexisting in the same codebase for no reason:

Load Functions + Form ActionsRemote Functions (query/form/command)
StatusStable, the current defaultExperimental — opt-in via svelte.config.js
Reads+page.server.ts / +page.ts load functionquery() called directly from any component
Mutationsexport const actions = {...} in +page.server.tsform() for form-shaped mutations, command() for everything else
Caching/dedupManual (or via depends/invalidate)Automatic, plus query.live() for streaming updates
Type safety across client/serverGood, but requires explicit PageData typingBuilt-in end-to-end
Best fitAny current SvelteKit projectTeams comfortable enabling experimental flags and revisiting call sites if the API shifts

A CLAUDE.md that doesn’t name which one applies leaves Claude Code to guess based on whichever pattern shows up more in its training data — which currently still skews toward load functions and form actions, since remote functions only stabilized enough to be commonly documented partway through 2026.

settings.json: Catching Svelte 4 Syntax Before It Lands

The single most common drift on a runes-based project is Claude Code reaching for $: and export let out of habit. A written rule helps, but a hook that actually checks the diff catches it even when the rule gets missed:

{
  "permissions": {
    "allow": [
      "Bash(npm run dev:*)",
      "Bash(npm run build:*)",
      "Bash(npm run check:*)",
      "Bash(npx svelte-check:*)",
      "Bash(git status)",
      "Bash(git diff:*)"
    ],
    "deny": [
      "Bash(npm publish:*)"
    ]
  }
}

Pair it with a PostToolUse hook that runs svelte-check and ESLint automatically after any .svelte or .svelte.ts edit, so a stray $: block gets flagged in the same turn it’s written instead of surviving until someone reviews the diff:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "if [[ \"$CLAUDE_FILE_PATH\" == *.svelte || \"$CLAUDE_FILE_PATH\" == *.svelte.ts ]]; then npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null; npx svelte-check --threshold error 2>/dev/null; fi"
          }
        ]
      }
    ]
  }
}

svelte-check won’t reject $: syntax on its own — it’s valid Svelte, just the wrong era for this project — so the ESLint step needs the eslint-plugin-svelte rule that flags legacy reactive statements enabled in the project’s own config for this to actually catch the pattern, not just formatting issues.

AGENTS.md for a SvelteKit App + Component Library Monorepo

Teams that publish a shared UI package alongside their SvelteKit app run into the same boundary problem Vue and React monorepos do — a component library that quietly picks up an app-specific dependency:

# AGENTS.md

## Global Rules
- Follow all rules in CLAUDE.md.
- Run `npm run check` and `npm run test` in every touched workspace before marking a task complete, not just the app.
- Never edit generated files under `.svelte-kit/` or `dist/`.

## /apps/web/
Agent scope: the SvelteKit application — routing, load functions/remote functions, pages.
- May import from `packages/ui` and `packages/utils`. Never the reverse.
- `$env` access only in `+page.server.ts`/`+layout.server.ts`/`hooks.server.ts` — never in a component under `packages/ui`.

## /packages/ui/
Agent scope: presentational Svelte components, published as a package.
- No SvelteKit imports (`$app/*`, `$env/*`, load functions). Components take data via `$props()` and emit via callback props, not custom events tied to app-specific handlers.
- Every exported component needs a Vitest test using `@testing-library/svelte` and a Storybook story if the workspace has Storybook configured.

## /packages/utils/
Agent scope: framework-agnostic helpers.
- No Svelte imports at all — plain TypeScript only, so the package stays usable outside a Svelte app if it's ever extracted.

The rule against $env and $app/* imports inside packages/ui matters more than it looks — those modules only resolve correctly inside a SvelteKit app, so a component library that imports them works fine in the monorepo and breaks the moment someone tries to consume the package from outside it.

Common Mistakes to Watch For

Mixing $: reactive statements with $derived() in the same file. Both compile, but a component with both patterns is harder to reason about — and without an enforced check, it’s an easy drift to introduce one file at a time, especially when Claude Code is working from an older code example in context.

Reaching for a remote function on a project that hasn’t enabled the experimental flags. query/form/command don’t exist until svelte.config.js opts in via kit.experimental.remoteFunctions and compilerOptions.experimental.async — code written assuming they’re available will fail to build on a project still on the stable load-function pattern.

Adding a new +server.ts endpoint for an internal mutation. If the frontend calling it is the same app, a form action (or a remote form()/command(), depending on which layer the project uses) handles progressive enhancement and avoids exposing an endpoint that didn’t need to be public.

Using process.env instead of $env/static/* or $env/dynamic/*. SvelteKit’s adapters don’t all populate process.env the same way — Cloudflare’s adapter, for instance, exposes bindings through platform.env instead — so code that reads process.env directly can work in dev and fail silently in production.

Assuming SvelteKit 3 conventions on a project still running SvelteKit 2. error() without a message, invalidateAll, and pushState/replaceState all still work on SvelteKit 2 — they’re deprecated in the @next preview, not gone from the stable release. Writing @next-only code on a stable project produces a build error, not a warning.


Svelte’s biggest 2026 gap in the Claude Code ecosystem isn’t capability — the compiler and CLI work fine without any special setup — it’s that the framework has two live data-layer conventions at once (load functions/form actions, stable; remote functions, experimental and actively evolving) plus a training-data bias toward pre-runes syntax that a written rule alone doesn’t fully stop. Name which data layer the project uses, enforce runes with a hook instead of just a CLAUDE.md line, and the monorepo boundaries above cover the rest of what causes review churn on real Svelte/SvelteKit projects.

Browse more real Svelte, TypeScript, and framework-specific CLAUDE.md/AGENTS.md examples in our gallery.


FAQ

Does Claude Code work well with Svelte and SvelteKit? Yes — the Svelte CLI, svelte-check, and SvelteKit’s dev/build commands all work without special setup. The gap is version drift: Svelte 5’s runes have been stable since October 2024, but plenty of training data still shows Svelte 4’s $: reactive statements, so an unguided Claude Code can write syntax that’s valid but wrong for a runes-based project.

Should I tell Claude Code to use remote functions or load functions? Name it explicitly in CLAUDE.md. Load functions and form actions are the stable, current default for SvelteKit data fetching and mutations. Remote functions (query/form/command) are experimental, opt-in, and positioned as SvelteKit’s eventual recommended approach — they’re not something Claude Code should assume is available unless the project’s svelte.config.js has actually enabled them.

How do I stop Claude Code from writing $: x = ... on a runes project? A CLAUDE.md rule helps but isn’t enforced. Pairing it with a PostToolUse hook that runs ESLint (with eslint-plugin-svelte’s legacy-reactive-statement rule enabled) after every .svelte edit catches it in the same turn it’s written, instead of relying on someone noticing it in review.

Is SvelteKit 3 stable yet? No — as of August 2026 it’s in @next preview, with thirteen preview releases shipped in July alone. It includes breaking changes (error() requiring a message, invalidateAll deprecated for refreshAll, pushState/replaceState collapsing into goto’s state option) that don’t apply to the stable SvelteKit 2 line. Pin your CLAUDE.md to whichever version the project actually runs.

How is this different from the gallery’s existing Svelte rule sets? The gallery’s SvelteKit + TypeScript and Svelte 5 migration rule sets are solid starting points for runes and basic SvelteKit conventions, but neither addresses remote functions, SvelteKit 3’s preview changes, or the component-library monorepo boundary. This guide is meant to sit on top of them for teams on a current Svelte 5/SvelteKit 2 (or @next) stack.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules