このページは英語でのみご覧いただけます。
SvelteKit
Pathruleルール2件 • メモリ4件 • スキル1件
A pattern bundle for SvelteKit 2 apps built on Svelte 5 runes. It encodes where data loading belongs, how to keep secrets server-side, and how form actions and remote functions stay type-safe and progressively enhanced. Use it so AI agents stop leaking private values into the client bundle and stop reaching for stores when runes are the right tool.
推奨されるパスの配置
Pathruleは各要素を対応するパスに配置するため、アシスタントはそれが属する場所でだけ参照します。これはインポート時に適用されるスコープで、ワークスペースで調整できます。
/ ワークスペースのルート
sveltekit-review
src/
Svelte 5 runes are the default; reactive classes replace stores
Async-in-components is experimental; reads after await are not tracked
routes/
Server-only data and secrets live in +page.server.ts
Mutations go through form actions or remote functions, never load
Avoid load waterfalls; stream non-critical data
Remote functions are experimental and the API is still moving
ルール
2Server-only data and secrets live in +page.server.ts/src/routes高強制Put auth, DB access, and private env in server load; keep universal load for public, serializable data.
| 1 | Any data that needs secrets, cookies, a database, or private APIs belongs in a server load function, not a universal one. |
| 2 | |
| 3 | - Use `+page.server.ts` / `+layout.server.ts` for `load` that touches `$env/static/private`, `$env/dynamic/private`, `cookies`, or `locals`. |
| 4 | - Reserve `+page.ts` / `+layout.ts` (universal) for public external APIs and non-serializable returns like component constructors. |
| 5 | - Never import anything from `$lib/server`, a `*.server.ts` module, or a `$env/.../private` module into universal load or any client-reachable code. SvelteKit treats the whole import chain as unsafe and Vite refuses to build it; that build error is the signal the logic is in the wrong file, not something to work around. |
| 6 | - When both load functions exist on a route, the server load runs first and its result reaches the universal load via the `data` property. Universal output cannot flow back to the server. |
| 7 | - Server load runs only on the server. Universal load runs on the server during SSR, then again in the browser on hydration and on client-side navigation, so it must never assume a server-only global is present. |
Mutations go through form actions or remote functions, never load/src/routes高助言Writes belong in form actions or remote form/command; load is read-only and reruns on navigation.
| 1 | `load` functions are for reading and must stay side-effect free, because SvelteKit reruns them on navigation, invalidation, and SSR. A write placed in `load` will silently re-fire. |
| 2 | |
| 3 | - Handle writes with named `actions` in `+page.server.ts` plus a `<form method="POST">`, or with remote `form` / `command` functions in a `.remote.ts` file. |
| 4 | - Validate input server-side. In a form action, return `fail(status, data)` on validation errors and a serializable object on success. |
| 5 | - Use the `enhance` action for progressive enhancement instead of a hand-rolled `fetch`, and call `invalidate('app:key')` / `invalidateAll()` to refresh affected `load` data after the mutation. |
| 6 | - Do not perform POST/PUT/DELETE logic inside a `load`, and do not call a mutating remote `command` from a `load`. |
メモリ
4Svelte 5 runes are the default; reactive classes replace stores/srcPrefer $state/$derived/$props; never use $effect to sync state; use reactive classes in $lib over writable stores.
| 1 | This codebase targets Svelte 5, so reactivity is rune-based, not store-based. |
| 2 | |
| 3 | - Use `$state` for mutable reactive values, `$derived` (or `$derived.by`) for computed values, `$props` for component inputs, and `$effect` only as an escape hatch for genuine side effects (DOM, analytics, subscriptions). |
| 4 | - Do not use `$effect` to synchronise or recompute state from other state. The docs are explicit: use `$derived` instead. Writing to state that the same effect reads also risks an infinite loop; reach for `untrack` only when you deliberately need to read without depending. |
| 5 | - Reach for `$state` only when a value drives the UI. Plain `const` / `let` is cheaper and clearer for everything else. |
| 6 | - For shared logic, write a reactive class in `$lib` whose fields use `$state` / `$derived`, and import it, instead of authoring `writable` / `readable` stores. Runes work inside plain `.svelte.ts` modules and classes. |
| 7 | - Avoid `$:` reactive statements and the implicit let-is-reactive model from Svelte 4. They do not exist in runes mode. |
Avoid load waterfalls; stream non-critical data/src/routesFire independent fetches before await parent(); return unawaited promises for slow, non-essential data.
| 1 | Load performance hinges on not serializing requests that could run in parallel. |
| 2 | |
| 3 | - Start independent `fetch` calls before `await parent()` so they do not block on parent data they do not need. |
| 4 | - Return unresolved promises from a server `load` for non-essential data. SvelteKit streams them to the client so the page renders before they settle, and the markup can `{#await}` them. |
| 5 | - Attach `.catch()` to any streamed promise that does NOT come from SvelteKit's injected `fetch`. An unhandled rejection in a streamed promise can crash the response. |
| 6 | - Use the injected `fetch` argument inside `load` (not global `fetch`) so SvelteKit forwards credentials and cookies, resolves relative URLs, inlines the response during SSR, and tracks the request as a dependency for `invalidate`. For custom clients that bypass `fetch`, call `depends('app:key')` to register a manual dependency. |
Remote functions are experimental and the API is still moving/src/routesquery/form/command/prerender in .remote.ts behind two experimental flags; validate args with a Standard Schema; expect breaking churn.
| 1 | Remote functions (available since SvelteKit 2.27) are still experimental as of mid-2026. Treat their surface as unstable and pin against the SvelteKit version in this repo before copying examples. |
| 2 | |
| 3 | - Enable them with BOTH flags: `kit.experimental.remoteFunctions: true` and `compilerOptions.experimental.async: true` in `svelte.config.js`. They live in `*.remote.ts` files and export `query` (read, deduped + cached), `form` (progressively-enhanced writes bound to `<form>`), `command` (writes from anywhere, not form-bound), and `prerender` (build-time static data). |
| 4 | - Every function that takes an argument must validate it with a Standard Schema validator (Zod, Valibot) passed as the first parameter. Do not trust raw input. |
| 5 | - Use single-flight mutations: inside a `form` / `command` handler, call `.refresh()` / `.set()` on affected queries so the mutation and the data refresh travel in one round-trip. Use `getRequestEvent()` for request context, and `query.batch` to collapse N parallel calls into one request and avoid n+1. |
| 6 | - Watch the breaking churn: `.run()` was removed from remote queries (await the query directly); `enhance` callbacks now receive a copy of the form instance rather than `{ form, data, submit }`; `query.live()` is the async-iterable real-time subscription helper. Confirm exact signatures against the installed version, not blog posts. |
Async-in-components is experimental; reads after await are not tracked/srcawait in script/$derived/markup needs experimental.async; reactive reads after await or in setTimeout are not tracked; use $effect.pending.
| 1 | Svelte 5 (since 5.36) lets you use `await` directly in component `<script>`, in `$derived`, and in markup, but it is experimental and must be opted into via `compilerOptions.experimental.async` in `svelte.config.js`. The flag is slated to be removed in Svelte 6. |
| 2 | |
| 3 | - Reactive values read ASYNCHRONOUSLY are not tracked. Anything read after an `await`, inside a `setTimeout`, or in a `.then()` callback will not register as a dependency, so the effect or derived will not re-run when it changes. Read the reactive value synchronously first, then await. |
| 4 | - Use `$effect.pending()` to know how many async operations are still settling in the current boundary (it does not count child boundaries) when you need loading UI. |
| 5 | - Svelte holds the UI in a consistent state while an `await` that depends on reactive state is in flight, rather than flashing intermediate values; rely on that instead of manual loading flags where possible. |
| 6 | - This is independent of SvelteKit `load` streaming. Prefer `load` + streamed promises for route data and reserve in-component `await` for leaf-level async that is local to one component. |
スキル
1sveltekit-review/rootPre-merge checklist for SvelteKit 2 routes: data boundaries, secrets, runes, mutations, and remote functions.
| 1 | --- |
| 2 | name: sveltekit-review |
| 3 | description: Review a SvelteKit 2 (Svelte 5) change before merge - verify load placement, secret isolation, runes usage, form actions, remote functions, and load performance. Use when reviewing or authoring routes, load functions, form actions, remote functions, or shared $lib reactivity. |
| 4 | --- |
| 5 | |
| 6 | # SvelteKit review |
| 7 | |
| 8 | ## Secrets and data boundaries |
| 9 | - [ ] Secrets, DB access, `cookies`, and `locals` appear only in `+page.server.ts` / `+layout.server.ts`, never in universal load or client code. |
| 10 | - [ ] No `$lib/server`, `*.server.ts`, or `$env/.../private` import reaches a `+page.ts`, `+layout.ts`, or component (the build would fail; if it builds, the chain is clean). |
| 11 | - [ ] Universal `load` returns only serializable data or intentional non-serializable values (components/classes); server `load` returns serializable data only. |
| 12 | |
| 13 | ## Mutations |
| 14 | - [ ] `load` functions are read-only; all writes go through `actions` or remote `form` / `command`. |
| 15 | - [ ] Form actions validate input server-side and use `fail(status, data)` for errors; `<form>` uses `enhance`. |
| 16 | - [ ] Data is refreshed after mutations via `invalidate` / `invalidateAll` (or remote single-flight `.refresh()` / `.set()`), not manual refetch. |
| 17 | |
| 18 | ## Reactivity |
| 19 | - [ ] Reactivity uses runes (`$state`, `$derived`, `$props`); no `$:` statements or new `writable` / `readable` stores where a reactive class fits. |
| 20 | - [ ] `$effect` is used only for true side effects, never to synchronise or recompute state that `$derived` should produce. |
| 21 | - [ ] If async-in-components is used, no reactive value is depended on only after an `await` / in a `setTimeout`. |
| 22 | |
| 23 | ## Load performance |
| 24 | - [ ] `load` uses the injected `fetch` argument; independent fetches start before `await parent()`. |
| 25 | - [ ] Slow non-critical data is streamed via returned promises, with `.catch()` on any promise not from the injected `fetch`. |
| 26 | |
| 27 | ## Remote functions (if used) |
| 28 | - [ ] Both `experimental.remoteFunctions` and `experimental.async` flags are set; functions live in `*.remote.ts`. |
| 29 | - [ ] Every argument-taking remote function validates input with a Standard Schema (Zod / Valibot). |
| 30 | - [ ] API usage matches the installed SvelteKit version (no removed `.run()`; correct `enhance` callback shape). |
このパターンを使う理由
AI agents leak private values into the client bundle and pick the wrong data-loading or reactivity primitive in SvelteKit.
対象:Teams shipping full-stack apps on SvelteKit 2 with Svelte 5
アシスタントが次のことをしないようにします。
- Importing $env/static/private or $env/dynamic/private into universal load or client code, leaking secrets into the bundle
- Putting database queries and auth in +page.ts universal load instead of +page.server.ts
- Reaching for writable stores where Svelte 5 runes ($state, $derived) are the correct primitive
- ライセンス
- Apache-2.0
- バージョン
- 1.0.1
- 更新日
- 2026-08-25