Claude CodeでSvelte/SvelteKit開発を効率化する実践ガイド
Claude CodeでSvelte/SvelteKit開発を進める実践ガイド。セットアップ、runes、フォーム、ルーティング、テストまで解説。
Claude CodeとSvelte/SvelteKitの相性
Svelteは、HTML・CSS・JavaScriptを書いたコンポーネントをビルド時に軽いJavaScriptへ変換するUIフレームワークです。SvelteKitはその上に、ファイルベースルーティング、サーバーサイドレンダリング、load関数、フォームアクション、APIエンドポイントを足したアプリケーションフレームワークです。公式ドキュメントでも、Svelteは「単体コンポーネントからフルスタックアプリまで」使えると説明されています。
Claude Codeは、ターミナルやIDEからコードベースを読み、ファイルを編集し、コマンドを実行できるエージェント型の開発ツールです。Svelte/SvelteKitでは、1つの機能変更が src/routes、src/lib/components、src/lib/server、テスト、型定義にまたがりやすいため、Claude Codeに「関連ファイルを読ませてから小さく直す」進め方が向いています。
初心者がつまずきやすいのは、Svelte 5のrunes(ルーン、つまりリアクティブな値を宣言する記法)と、SvelteKitのサーバー/クライアント境界です。この記事では、Masaが小さなタスク管理アプリを作る前提で、セットアップ、コンポーネント、共有状態、ルーティング、フォーム、テスト、Claude Codeへの安全な頼み方まで一通りつなげます。
flowchart LR
A["要件を小さく書く"] --> B["Claude Codeに関連ファイルを読ませる"]
B --> C["Svelteコンポーネントを変更"]
C --> D["SvelteKitのload/actionsを確認"]
D --> E["型チェックとテストを実行"]
E --> F["git diffで人間がレビュー"]
プロジェクトセットアップ
新しくSvelteKitを始めるなら、公式のSvelte CLIである sv を使うのが現在の基本です。公式のSvelteKit作成手順は npx sv create my-app から始まります。既存のViteベースのSvelte単体アプリを作る場合は、Vite公式の svelte-ts テンプレートも使えます。Viteの公式ガイドは、Vite自体にNode.js 20.19以上または22.12以上を求めているため、古いNode環境では先に更新してください。
Claude Codeは公式ドキュメント上で、コードベースを読み、編集し、コマンドを実行できるツールとして説明されています。Windowsではネイティブ実行かWSLを選べますが、SvelteKitのNodeツールチェーンを安定させたいなら、チームで使うNodeバージョンを先に決めておくと事故が減ります。
# SvelteKitアプリを新規作成
npx sv create claude-svelte-demo
cd claude-svelte-demo
npm install
npm run dev
# Claude Codeをプロジェクト直下で起動
claude
Claude Codeに最初に渡す依頼は、いきなり「アプリを作って」ではなく、境界を含めて書きます。
/plan
このSvelteKitプロジェクトにタスク一覧機能を追加したいです。
まず src/routes と src/lib の構成を読み、変更対象ファイル、データの流れ、テスト方針を提案してください。
まだファイルは編集しないでください。
Claude Codeの公式権限モードには、編集前に調査と計画だけを行うplan modeがあります。慣れるまでは claude --permission-mode plan で始め、計画に納得してから編集に進むと、SvelteKitのルーティングやサーバー処理を壊しにくくなります。プロジェクト固有のルールは CLAUDE.md に書けます。たとえば「Svelte 5のrunes構文を優先」「フォームはSvelteKit actionsを使う」「秘密情報は $lib/server から外に出さない」といったルールです。
コンポーネントはSvelte 5の書き方で小さく作る
Svelte 5では、props(親から子へ渡す入力)は $props() で受け取り、状態は $state、派生値は $derived で表現します。$derived は他の状態から計算される値で、副作用を入れないのが基本です。初心者向けに言うと、$state は「変わる元データ」、$derived は「元データから自動計算される表示用データ」です。
次の TaskCard.svelte は、そのまま src/lib/components/TaskCard.svelte に置ける小さなコンポーネントです。親からタスクとクリック時の関数を受け取り、表示とアクセシビリティをまとめています。
<!-- src/lib/components/TaskCard.svelte -->
<script lang="ts">
type Task = {
id: string;
title: string;
done: boolean;
estimateMinutes: number;
tags: string[];
};
let {
task,
onToggle
}: {
task: Task;
onToggle: (id: string) => void;
} = $props();
let statusLabel = $derived(task.done ? '完了' : '未完了');
let estimateLabel = $derived(`${Math.ceil(task.estimateMinutes / 15) * 15}分枠`);
</script>
<article class:done={task.done} class="task-card">
<div>
<p class="status">{statusLabel}</p>
<h3>{task.title}</h3>
<p>{estimateLabel}</p>
</div>
<ul aria-label="タグ">
{#each task.tags as tag}
<li>{tag}</li>
{/each}
</ul>
<button type="button" aria-pressed={task.done} onclick={() => onToggle(task.id)}>
{task.done ? '未完了に戻す' : '完了にする'}
</button>
</article>
<style>
.task-card {
display: grid;
gap: 0.75rem;
border: 1px solid #ddd;
border-radius: 0.5rem;
padding: 1rem;
}
.done {
background: #f2fff5;
}
.status {
font-size: 0.875rem;
font-weight: 700;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
padding: 0;
}
li {
border-radius: 999px;
background: #eef2ff;
padding: 0.2rem 0.6rem;
}
</style>
Claude Codeにコンポーネントを直してもらうときは、「見た目をいい感じに」だけではなく、入出力と制約を渡します。
src/lib/components/TaskCard.svelte をSvelte 5のrunes構文のまま改善してください。
条件:
- propsの型は維持
- onclickを使い、旧式の on:click へ戻さない
- ボタンのaria-pressedを維持
- CSSだけでカード幅が崩れないようにする
- 変更後にこのコンポーネントのテスト案も出す
このように書くと、Claude Codeが勝手にAPIを変えたり、Svelte 4風の export let に戻したりするリスクを下げられます。
共有状態はrunesとstoresを使い分ける
Svelte 5では .svelte.ts ファイル内でもrunesを使えます。公式ドキュメントでは、これは再利用可能なリアクティブロジックや共有状態に便利だと説明されています。一方、svelte/store は今でも有効で、複雑な非同期ストリームや手動購読が必要な場合に向いています。
タスク一覧の検索条件程度なら、.svelte.ts に共有状態を置くと読みやすくなります。
// src/lib/state/taskFilters.svelte.ts
export type TaskStatus = 'all' | 'open' | 'done';
export const taskFilters = $state({
query: '',
status: 'all' as TaskStatus,
tag: ''
});
export function resetTaskFilters() {
taskFilters.query = '';
taskFilters.status = 'all';
taskFilters.tag = '';
}
<!-- src/lib/components/TaskFilterPanel.svelte -->
<script lang="ts">
import { resetTaskFilters, taskFilters } from '$lib/state/taskFilters.svelte';
</script>
<section aria-label="タスク絞り込み">
<label>
キーワード
<input bind:value={taskFilters.query} placeholder="請求書、記事、レビューなど" />
</label>
<label>
状態
<select bind:value={taskFilters.status}>
<option value="all">すべて</option>
<option value="open">未完了</option>
<option value="done">完了</option>
</select>
</label>
<button type="button" onclick={resetTaskFilters}>リセット</button>
</section>
ここでの落とし穴は、SSR(サーバーサイドレンダリング、初回表示をサーバーで作る仕組み)を忘れることです。localStorage や window はブラウザにしかありません。保存が必要な設定を扱うなら、$app/environment の browser を見て、ブラウザ側だけで実行します。
// src/lib/state/theme.svelte.ts
import { browser } from '$app/environment';
export const themeState = $state({
theme: 'system' as 'system' | 'light' | 'dark'
});
export function loadTheme() {
if (!browser) return;
const saved = localStorage.getItem('theme');
if (saved === 'light' || saved === 'dark' || saved === 'system') {
themeState.theme = saved;
}
}
export function saveTheme(nextTheme: typeof themeState.theme) {
themeState.theme = nextTheme;
if (browser) localStorage.setItem('theme', nextTheme);
}
ルーティングとload関数をClaude Codeに読ませる
SvelteKitの中心はファイルベースルーティングです。公式ドキュメントの例の通り、src/routes/about は /about、src/routes/blog/[slug] は動的な slug パラメータを持つルートになります。ページ表示前にデータが必要なら、同じディレクトリに +page.server.ts または +page.ts を置きます。
サーバーだけで読ませたい処理は $lib/server に寄せます。たとえばDBや秘密キーに触れる処理をコンポーネントから直接importしない、という境界が大切です。
// src/lib/server/tasks.ts
export type Task = {
id: string;
slug: string;
title: string;
done: boolean;
estimateMinutes: number;
tags: string[];
};
const tasks: Task[] = [
{
id: 'task-1',
slug: 'write-svelte-guide',
title: 'SvelteKit記事の下書きを作る',
done: false,
estimateMinutes: 45,
tags: ['writing', 'svelte']
}
];
export async function getTaskBySlug(slug: string) {
return tasks.find((task) => task.slug === slug) ?? null;
}
// src/routes/tasks/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { getTaskBySlug } from '$lib/server/tasks';
export const load: PageServerLoad = async ({ params }) => {
const task = await getTaskBySlug(params.slug);
if (!task) {
error(404, 'Task not found');
}
return { task };
};
<!-- src/routes/tasks/[slug]/+page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<svelte:head>
<title>{data.task.title} | Tasks</title>
</svelte:head>
<article>
<p>{data.task.done ? '完了' : '未完了'}</p>
<h1>{data.task.title}</h1>
<p>見積もり: {data.task.estimateMinutes}分</p>
</article>
Claude Codeにこの周辺を変更してもらう場合は、「src/routes/tasks/[slug] と $lib/server/tasks.ts を読んでから、Task 型を壊さずに項目を追加して」と頼みます。SvelteKitではルート名が型生成に影響するため、ディレクトリ名の変更は影響範囲を必ず確認してからにします。
フォームはactionsで段階的に強くする
SvelteKitのform actionsは、+page.server.ts から actions をexportし、HTMLの <form method="POST"> でサーバーに送る仕組みです。公式ドキュメントは、フォームはJavaScriptなしでも動き、必要に応じて use:enhance で段階的に体験を良くできると説明しています。
問い合わせフォームの最小例を作るなら、まずサーバー側で値を検証し、失敗時は fail で入力値とエラーを返します。
// src/routes/contact/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const values = {
name: String(formData.get('name') ?? '').trim(),
email: String(formData.get('email') ?? '').trim(),
message: String(formData.get('message') ?? '').trim()
};
const errors: Record<string, string> = {};
if (values.name.length < 2) errors.name = '名前は2文字以上で入力してください';
if (!values.email.includes('@')) errors.email = 'メールアドレスを確認してください';
if (values.message.length < 10) errors.message = '相談内容は10文字以上で入力してください';
if (Object.keys(errors).length > 0) {
return fail(400, { values, errors });
}
console.log('New inquiry', values);
return { success: true };
}
} satisfies Actions;
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageProps } from './$types';
let { form }: PageProps = $props();
</script>
{#if form?.success}
<p role="status">送信しました。1から3営業日以内に返信します。</p>
{/if}
<form method="POST" use:enhance>
<label>
名前
<input name="name" value={form?.values?.name ?? ''} aria-invalid={!!form?.errors?.name} />
</label>
{#if form?.errors?.name}<p>{form.errors.name}</p>{/if}
<label>
メール
<input name="email" type="email" value={form?.values?.email ?? ''} aria-invalid={!!form?.errors?.email} />
</label>
{#if form?.errors?.email}<p>{form.errors.email}</p>{/if}
<label>
相談内容
<textarea name="message" rows="5" aria-invalid={!!form?.errors?.message}>{form?.values?.message ?? ''}</textarea>
</label>
{#if form?.errors?.message}<p>{form.errors.message}</p>{/if}
<button type="submit">送信する</button>
</form>
注意点は、GETで副作用を起こさないこと、秘密キーをクライアント側に出さないこと、{@html} で未検証の本文を表示しないことです。Claude Codeにフォーム改修を頼むなら、「アクセシビリティ属性を残す」「JS無効でもPOSTできる」「サーバー側検証を削らない」と明記します。
テストと確認コマンド
Svelte公式のテストページは、ViteやSvelteKitではVitestが推奨しやすい選択肢だと説明しています。コンポーネントはTesting Libraryでユーザー操作に近い形を確認し、SvelteKit全体の画面遷移はPlaywrightでE2Eテストに回すのが扱いやすいです。
// src/lib/components/TaskCard.test.ts
import { fireEvent, render, screen } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import TaskCard from './TaskCard.svelte';
describe('TaskCard', () => {
it('toggles the task when the button is clicked', async () => {
let toggledId = '';
render(TaskCard, {
task: {
id: 'task-1',
title: 'SvelteKit記事を書く',
done: false,
estimateMinutes: 45,
tags: ['writing']
},
onToggle: (id) => {
toggledId = id;
}
});
await fireEvent.click(screen.getByRole('button', { name: '完了にする' }));
expect(toggledId).toBe('task-1');
});
});
npm run check
npm run test
npm run build
git diff -- src/lib src/routes
Claude Codeに最後の確認まで頼む場合は、コマンド名を明示します。
ここまでの変更について npm run check と npm run test を実行してください。
失敗した場合は、最初のエラーだけを読んで原因を説明し、最小差分で直してください。
修正後は git diff -- src/lib src/routes の要点をまとめてください。コミットはしないでください。
3つの実用ユースケース
| ユースケース | Claude Codeに任せやすい作業 | 人間が確認すること |
|---|---|---|
| 管理画面のフィルターUI | $state と $derived で検索条件を整理し、コンポーネントを分割する | URLクエリに残す条件、権限で隠す項目 |
| ブログやCMSの詳細ページ | src/routes/[slug]、load、SEO用の <svelte:head> をそろえる | {@html} のサニタイズ、404や下書きの扱い |
| 問い合わせ/リード獲得フォーム | actions、バリデーション、use:enhance、テストを一式作る | 個人情報の保存先、通知先、スパム対策 |
| Svelte 4からSvelte 5への移行 | export let や $: をrunesへ段階移行する | 一括変換しすぎて挙動が変わっていないか |
収益化の観点では、SvelteKitのフォームと記事導線は相性が良いです。たとえばClaudeCodeLabなら、技術記事から Claude Code導入相談 へ自然に案内し、開発自動化やチーム導入の相談につなげられます。関連して、Claude Codeの基本操作は Claude Code入門ガイド、型の扱いは TypeScript活用テクニック、検証方針は テスト戦略ガイド も合わせて読むと理解しやすいです。
失敗例と落とし穴
1つ目の失敗は、Claude Codeに大きすぎる依頼を投げることです。「SvelteKitでSaaSを作って」では、ルート、状態管理、DB、認証、UI方針が混ざりすぎます。「まず /tasks の一覧だけ」「フォームactionだけ」「既存CSSは触らない」のように小さく切るほうが安定します。
2つ目は、Svelte 5とSvelte 4の書き方が混ざることです。既存プロジェクトがrunesで統一されているなら、Claude Codeへの依頼にも「$props、$state、$derived を使い、export let に戻さない」と書きます。逆に既存がSvelte 4のままなら、勝手に全移行させず、対象コンポーネントを限定します。
3つ目は、サーバー専用コードをクライアントへ漏らすことです。$lib/server のモジュールを .svelte コンポーネントにimportしない、環境変数や秘密キーをフォーム画面に渡さない、DB更新はactionsやserver routesに閉じる、という境界を守ります。
4つ目は、$effect をデータ計算に使いすぎることです。公式ドキュメントでも、$effect はブラウザだけで動き、状態更新を中に入れると複雑になりやすいと説明されています。表示用の計算はまず $derived、外部ライブラリやCanvas、ブラウザAPIとの同期だけ $effect と覚えると安全です。
5つ目は、テストをClaude Code任せで読まないことです。テストが「ボタンが存在する」だけなら、壊れた送信処理を見逃します。最低でも、入力、クリック、エラー表示、成功表示、load の404を確認するテストにします。
公式リンクと頼み方テンプレート
最新仕様を確認するなら、Svelte公式ドキュメント、SvelteKit公式ドキュメント、SvelteKit form actions、Vite公式ガイド、Claude Code公式ドキュメント を一次情報にします。AIに聞くときも「公式ドキュメントの現在の書き方に合わせて」と添えると、古いAPIに戻る確率を下げられます。
最後に、実務で使いやすい依頼テンプレートです。
SvelteKitの現在の構成を読んでから、次の範囲だけ変更してください。
対象: src/routes/contact/+page.svelte と src/routes/contact/+page.server.ts
目的: 問い合わせフォームに会社名フィールドを追加
制約:
- Svelte 5のrunes構文を維持
- use:enhanceを削除しない
- サーバー側バリデーションを追加
- 既存のCTA文言とスタイルは変更しない
- npm run check が通るまで直す
最後に変更点、リスク、追加で必要なテストを3行でまとめてください。
まとめと試した結果
Claude CodeでSvelte/SvelteKit開発を進めるコツは、Svelteのrunes、SvelteKitのルート規約、サーバー/クライアント境界を明示しながら、小さな変更単位で依頼することです。コンポーネントは $props、$state、$derived で整理し、フォームはactionsでサーバー検証を残し、load 関数と $lib/server の責務を分けると、Claude Codeの編集結果もレビューしやすくなります。
この記事で紹介した内容を実際に試した結果、Masaの検証用SvelteKitタスク管理アプリでは、最初にplan modeでファイル構成を読ませたときが一番手戻りが少なくなりました。特に「旧式構文へ戻さない」「npm run check を最後に実行」「コミットしない」と依頼文に書いたことで、レビュー対象が小さくなり、Svelte初心者でも差分を追いやすくなりました。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。