sveltekit-best-practices について
sveltekit-best-practicesは、SvelteKitまたはSvelte 5を扱うコーディングエージェント向けの開発スキルです。Svelte 5のリアクティビティと、データ読み込み、フォームアクション、ルーティングに関するSvelteKitの慣習を示し、Svelte 4のパターンを避けます。
主な機能
- 01Svelte 5 runesによるリアクティブ状態管理
- 02$props()によるコンポーネントプロパティ宣言
- 03load関数によるページデータ取得
- 04フォームアクションによる送信処理
- 05レイアウト読み込みによる共有データ
- 06SvelteKitのエラーページとhooksの慣習
活用シーン
- 01Svelte 5コンポーネントとリアクティブ処理の作成
- 02サーバー側でのページデータ読み込み
- 03プログレッシブエンハンスメント対応フォームの構築
- 04共有レイアウトデータとリクエストミドルウェアの設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ofershap/ sveltekit-best-practices --skill sveltekit-best-practicesインストール後、Claude Code で/sveltekit-best-practicesを入力して読み込まれたか確認します。
詳細
使用する場面
SvelteKit または Svelte 5 のコードを扱うときに、このスキルを使用してください。AI エージェントは Svelte 4 のパターンで学習しているため、stores、リアクティブ宣言、export let を使った古いコードを頻繁に生成します。このスキルでは、Svelte 5 のルーン、load 関数、フォームアクションを徹底します。
重要なルール
1. Svelte 5 のルーンを使う — Svelte 4 の stores やリアクティブ宣言は使わない
誤り(エージェントがよく生成するパターン):
<script>
import { writable, derived } from 'svelte/store';
let count = writable(0);
$: doubled = $count * 2;
$: if (count > 5) alert('too high');
</script>
<p>{$count}</p>
正しい例:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
if (count > 5) alert('too high');
});
</script>
<p>{count}</p>
理由: Svelte 5 のルーン($state、$derived、$effect)は、stores と $: 構文に取って代わります。エージェントは Svelte 4 のパターンをデフォルトで使用します。
2. リアクティブな状態には $state を使う — リアクティブな代入を伴う let は使わない
誤り:
<script>
let count = 0;
count = count + 1;
</script>
正しい例:
<script>
let count = $state(0);
count = count + 1;
</script>
理由: Svelte 5 では、$state を使ってリアクティビティを明示的に有効にします。通常の let はリアクティブではありません。
3. 計算値には $derived を使う — $: のリアクティブ宣言は使わない
誤り:
<script>
let firstName = $state('John');
let lastName = $state('Doe');
$: fullName = `${firstName} ${lastName}`;
</script>
正しい例:
<script>
let firstName = $state('John');
let lastName = $state('Doe');
let fullName = $derived(`${firstName} ${lastName}`);
</script>
理由: $: は Svelte 4 の構文です。Svelte 5 では、派生値に $derived を使います。
4. 副作用には $effect を使う — $: のリアクティブステートメントは使わない
誤り:
<script>
let count = $state(0);
$: if (count > 5) console.log('count is high');
</script>
正しい例:
<script>
let count = $state(0);
$effect(() => {
if (count > 5) console.log('count is high');
});
</script>
理由: $effect は依存関係が変化したときに実行されます。副作用に $: を使う方法は非推奨です。
5. コンポーネントの props には $props() を使う — export let は使わない
誤り:
<script>
export let title = 'Default';
export let count;
</script>
<h1>{title}</h1>
正しい例:
<script>
let { title = 'Default', count } = $props();
</script>
<h1>{title}</h1>
理由: export let は Svelte 4 の構文です。Svelte 5 では $props() を使います。
6. 双方向バインディングを行う props には $bindable() を使う
誤り:
<script>
let { value } = $props();
</script>
<input bind:value={value} />
正しい例:
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value={value} />
理由: props はデフォルトで一方向です。$bindable() を使うと、親から bind:value を指定できるようになります。
7. データ取得には load 関数(+page.server.ts)を使う — onMount での fetch は使わない
誤り:
<script>
import { onMount } from 'svelte';
let data = $state(null);
onMount(async () => {
data = await fetch('/api/users').then(r => r.json());
});
</script>
{#if data}{data.name}{/if}
正しい例:
// +page.server.ts
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ fetch }) => ({
data: await fetch("/api/users").then((r) => r.json()),
});
<script>
let { data } = $props();
</script>
{#if data}{data.name}{/if}
理由: load は SSR のためにサーバーで実行されるので、読み込み時のちらつきを防ぎ、SvelteKit のルーティングと連携します。
8. データの変更にはフォームアクションを使う — フォーム送信に API ルートは使わない
誤り:
<form on:submit={async (e) => {
e.preventDefault();
await fetch('/api/login', { method: 'POST', body: new FormData(e.target) });
goto('/dashboard');
}}>
正しい例:
// +page.server.ts
import type { Actions } from "./$types";
export const actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
// validate, authenticate, set cookie
return { type: "redirect", location: "/dashboard" };
},
};
<form method="POST" use:enhance>
理由: フォームアクションはプログレッシブエンハンスメントを可能にし、JS なしでも動作するため、クライアント側で fetch の定型コードを書く必要がありません。
9. 共通のレイアウトデータには +layout.server.ts を使う
誤り:
<script>
let user = $state(null);
onMount(() => fetchUser().then(u => user = u));
</script>
正しい例:
// +layout.server.ts
export const load = async ({ locals }) => ({
user: locals.user,
});
理由: レイアウトの load は一度だけ実行され、データはすべての子ページから利用できます。重複した fetch は発生しません。
10. エラーページには +error.svelte を使う
正しい例:
<script>
let { status, message } = $props();
</script>
<h1>{status}</h1>
<p>{message}</p>
理由: SvelteKit は load やアクションで発生したエラーを +error.svelte で表示します。各ページで try/catch を使う代わりに、これを使用してください。
11. ユニバーサル load(サーバーとクライアント)には +page.ts を使う
サーバーとクライアントの両方でデータが必要な場合(例:$app/stores やブラウザー API から取得する場合)は、ロジックを +page.ts に記述してください。サーバーだけで使用するデータには +page.server.ts を使ってください。
12. ミドルウェア(認証、リダイレクト)には hooks.server.ts を使う
正しい例:
// hooks.server.ts
export const handle = async ({ event, resolve }) => {
event.locals.user = await getUser(event);
if (!event.locals.user && event.url.pathname.startsWith("/dashboard")) {
return redirect(302, "/login");
}
return resolve(event);
};
理由: handle はリクエストごとに実行されます。認証、リダイレクト、locals の設定に使用してください。
13. $app/stores は必要最小限にとどめる — load 関数のデータを優先する
load の props を使ってデータを渡してください。$page、$navigating などは、クライアント側のルーティング状態が必要な場合にのみ使用してください。
14. 再利用可能なテンプレートのまとまりには snippet ブロックを使う(Svelte 5)
誤り:
<script>
export let slots;
</script>
{#if slots.header}<slot name="header" />{/if}
正しい例:
<script>
let { header = @render(() => {}) } = $props();
</script>
{@render header()}
理由: Svelte 5 の snippet は、slot ベースの構成に取って代わり、@render と snippet props を使います。
パターン
- ページデータ:+page.server.ts の load はオブジェクトを返し、+page.svelte は $props() で
dataを受け取ります - enhance を使うフォーム:
method="POST"と$app/formsのuse:enhance - ストリーミング:await せずに load から Promise を返し、テンプレートで
{#await data.promise}を使います - TypeScript:
import type { PageServerLoad, PageProps } from './$types'を使います
アンチパターン
- svelte/store の writable、readable、derived、get を使わないでください
- 派生値や副作用に $: を使わないでください
- props に export let を使わないでください
- SSR に必要なデータを onMount で fetch しないでください
- フォームの POST 処理だけを目的に +server.ts API ルートを作成しないでください
- prop が $bindable() でない限り、bind:value を使わないでください