取り組みの背景:なぜ SvelteKit × Claude Code なのか
Webフレームワークの選択肢が多い2026年において、SvelteKitは独自の地位を確立しています。コンパイル型アーキテクチャによるバンドルサイズの小ささ、Svelte 5で導入されたRunes構文の直感性、そしてCloudflare WorkersやVercelへのエッジデプロイの容易さ——これらが開発者から高い評価を受けています。
一方、Claude Codeは単なるコード補完を超えた「自律的な開発パートナー」へと進化しています。SvelteKitとClaude Codeの組み合わせは、特に以下の場面で威力を発揮します。
- Svelte 5 Runesの学習曲線を短縮する:
$state・$derived・$effectといった新しいリアクティビティAPIを、Claude Codeが既存コードに適用しながら解説してくれる
- 型安全なAPIルート実装:SvelteKitのServer Actionsとload関数の型定義をClaude Codeが自動生成し、実行時エラーを未然に防ぐ
- テスト駆動の開発サイクル:Vitest + Playwright のテストコードをClaude Codeに先行作成させ、実装の方針を明確にする
第1章:プロジェクトのセットアップと CLAUDE.md 設計
SvelteKit プロジェクトの初期化
まず、Claude Codeにプロジェクト生成を任せます。ただし、技術スタックの選定は人間が先に決めておく必要があります。
# プロジェクト作成(Claude Code に任せる前に自分で実行)
npm create svelte@latest my-sveltekit-app
# → TypeScript + ESLint + Prettier + Vitest + Playwright を選択
cd my-sveltekit-app
# 依存パッケージの追加(Claude Code に指示する前に一覧を決定)
npm install drizzle-orm @auth/sveltekit
npm install -D drizzle-kit wrangler
CLAUDE.md の設計:SvelteKit 専用ルール
SvelteKitプロジェクトでClaude Codeを効果的に使うには、CLAUDE.mdに適切な制約を設定することが鍵になります。以下は実践的なテンプレートです。
# CLAUDE.md — SvelteKit Project
## 技術スタック
- Framework: SvelteKit 2.x + Svelte 5 (Runes API)
- Language: TypeScript(strict mode)
- Database: Cloudflare D1 + Drizzle ORM
- Auth: Auth.js v5 (@auth/sveltekit)
- Styling: TailwindCSS v4
- Test: Vitest (unit) + Playwright (e2e)
- Deploy: Cloudflare Workers (via wrangler)
## コーディング規約
- Svelte 5 Runes を使用($state, $derived, $effect, $props)
- 旧式のexport let は使用禁止
- load関数の返り値は必ず型定義する
- Server Actionsはactions オブジェクトとして export する
- コンポーネントファイルは src/lib/components/ 配下
- ページ固有コンポーネントは同ディレクトリ内に置く
## 禁止事項
- window/document への直接アクセス(SSR対応のためbrowserチェック必須)
- any型の使用
- console.log の本番コードへの混入
## テスト方針
- 全コンポーネントにVitest単体テストを作成する
- フォーム送信・認証フローはPlaywrightでE2Eテストを作成する
このCLAUDE.mdがあることで、Claude Codeはexport letではなく$props()を使い、適切なSSR対応コードを生成するようになります。
第2章:Svelte 5 Runes を活用したコンポーネント設計
Claude Code への指示パターン
Svelte 5のRunes APIは、従来のSvelte 4とは記法が大きく異なります。Claude Codeへの指示では、この違いを明示することが要点になります。
# Claude Code への指示例
「商品カードコンポーネント ProductCard.svelte を作成してください。
- Props: product: { id: string; name: string; price: number; imageUrl: string }
- Svelte 5 の $props() を使用する
- 数量選択のローカル状態は $state() で管理する
- 合計金額は $derived() で計算する
- カートに追加ボタンのクリックイベントは onaddtocart コールバックで親に渡す
- TailwindCSS でスタイリング
- Vitest でのテストコードも同時に作成する」
上記の指示に対して、Claude Codeが生成するコードの例です。
<!-- src/lib/components/ProductCard.svelte -->
<script lang="ts">
interface Product {
id: string;
name: string;
price: number;
imageUrl: string;
}
interface Props {
product: Product;
onaddtocart?: (productId: string, quantity: number) => void;
}
const { product, onaddtocart }: Props = $props();
// ローカル状態($state)
let quantity = $state(1);
// 派生値($derived)— 自動的にquantityの変更を追跡
const totalPrice = $derived(product.price * quantity);
function handleAddToCart() {
onaddtocart?.(product.id, quantity);
}
</script>
<div class="rounded-xl border border-gray-200 p-4 shadow-sm hover:shadow-md transition-shadow">
<img src={product.imageUrl} alt={product.name} class="w-full h-48 object-cover rounded-lg mb-3" />
<h3 class="font-semibold text-gray-900 text-lg">{product.name}</h3>
<p class="text-gray-500 text-sm mt-1">¥{product.price.toLocaleString()}</p>
<div class="flex items-center gap-3 mt-4">
<label for="qty-{product.id}" class="text-sm text-gray-600">数量</label>
<input
id="qty-{product.id}"
type="number"
bind:value={quantity}
min="1"
max="99"
class="w-16 border rounded px-2 py-1 text-center"
/>
<span class="text-sm text-gray-700">合計: ¥{totalPrice.toLocaleString()}</span>
</div>
<button
onclick={handleAddToCart}
class="mt-4 w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
カートに追加
</button>
</div>
$effect の使い所と落とし穴
$effectはReactのuseEffectに相当しますが、依存関係の追跡が自動です。Claude Codeに使わせる際の注意点をCLAUDE.mdに追記しておきましょう。
// ✅ 良い使い方:外部システムとの同期
$effect(() => {
const subscription = websocket.subscribe(channel, (data) => {
messages = [...messages, data];
});
return () => subscription.unsubscribe(); // クリーンアップ
});
// ❌ 避けるべき使い方:$derivedで代替できる場合
$effect(() => {
filteredItems = items.filter(item => item.active); // $derivedを使うべき
});
第3章:型安全な API ルートと Server Actions
SvelteKit のロードシステム
SvelteKitのload関数とServer Actionsは、型安全な実装がやや複雑です。Claude Codeに指示する際は、具体的な型情報を渡すことが成功の鍵です。
# Claude Code への指示例
「商品一覧ページ src/routes/products/+page.server.ts を作成してください。
- Drizzle ORM でproductsテーブルからデータを取得するload関数を実装
- カテゴリフィルタ(URLパラメータ ?category=)に対応
- ユーザーがカートに追加できるServer Actionを追加
- PageServerLoad, Actions 型を正しく使用する
- データベースはenv.DB(Cloudflare D1)を使用」
Claude Codeが生成するサーバーサイドコードの例です。
// src/routes/products/+page.server.ts
import type { PageServerLoad, Actions } from './$types';
import { fail } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { products, cartItems } from '$lib/server/schema';
import { eq } from 'drizzle-orm';
export const load: PageServerLoad = async ({ url, locals }) => {
const category = url.searchParams.get('category');
// Drizzle ORM でデータ取得
const query = db.select().from(products);
if (category) {
query.where(eq(products.category, category));
}
const productList = await query;
return {
products: productList,
currentCategory: category,
};
};
export const actions: Actions = {
addToCart: async ({ request, locals }) => {
const session = await locals.auth();
if (!session?.user?.id) {
return fail(401, { error: 'ログインが必要です' });
}
const data = await request.formData();
const productId = data.get('productId')?.toString();
const quantity = parseInt(data.get('quantity')?.toString() ?? '1');
if (!productId) {
return fail(400, { error: '商品IDが必要です' });
}
await db.insert(cartItems).values({
userId: session.user.id,
productId,
quantity,
}).onConflictDoUpdate({
target: [cartItems.userId, cartItems.productId],
set: { quantity },
});
return { success: true };
},
};
Drizzle ORM スキーマ設計を Claude Code に任せる
データベーススキーマの設計はClaude Codeが特に得意とする領域です。ER図や要件を自然言語で渡すだけで、型安全なDrizzleスキーマを生成してくれます。
// src/lib/server/schema.ts(Claude Code が生成)
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
});
export const products = sqliteTable('products', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
name: text('name').notNull(),
description: text('description'),
price: real('price').notNull(),
imageUrl: text('image_url'),
category: text('category').notNull(),
stock: integer('stock').notNull().default(0),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
});
export const cartItems = sqliteTable('cart_items', {
userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
productId: text('product_id').notNull().references(() => products.id, { onDelete: 'cascade' }),
quantity: integer('quantity').notNull().default(1),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
});
第4章:Auth.js v5 による認証実装
Cloudflare D1 × Auth.js のセットアップ
Auth.js v5(旧NextAuth.js)はSvelteKitに正式対応しており、Claude Codeとの相性も良いです。ただし、設定ファイルの構造がv4から大きく変わっているため、Claude Codeへの指示時にはバージョンを明示しましょう。
# Claude Code への指示
「Auth.js v5 (@auth/sveltekit) でGitHub OAuthとメール認証を設定してください。
- アダプター: Drizzle Adapter (drizzle-orm)
- データベース: Cloudflare D1
- セッション戦略: database
- src/auth.ts にSvelteKitHandlers export
- src/hooks.server.ts でhandle hookとして適用
- 型安全なlocals.auth() を使えるよう declare module も設定」
// src/auth.ts(Claude Code が生成)
import { SvelteKitAuth } from '@auth/sveltekit';
import { DrizzleAdapter } from '@auth/drizzle-adapter';
import GitHub from '@auth/core/providers/github';
import Resend from '@auth/core/providers/resend';
import { db } from '$lib/server/db';
import { env } from '$env/dynamic/private';
export const { handle, auth, signIn, signOut } = SvelteKitAuth({
adapter: DrizzleAdapter(db),
providers: [
GitHub({
clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
}),
Resend({
apiKey: env.RESEND_API_KEY,
from: 'noreply@yourdomain.com',
}),
],
session: { strategy: 'database' },
callbacks: {
session({ session, user }) {
session.user.id = user.id;
return session;
},
},
});
ルートガードの実装
認証が必要なページをClaude Codeに守らせる際は、SvelteKitの+layout.server.tsを活用します。
// src/routes/(protected)/+layout.server.ts
import type { LayoutServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
export const load: LayoutServerLoad = async ({ locals }) => {
const session = await locals.auth();
if (!session) {
redirect(303, '/login');
}
return { session };
};
第5章:Cloudflare Workers へのデプロイ自動化
wrangler.toml と $env/dynamic/private の設定
SvelteKitをCloudflare Workersにデプロイする際、環境変数の取り扱いに注意が必要です。Claude Codeに設定ファイルを生成させる前に、必要なバインディングを明確にしておきましょう。
# wrangler.toml(Claude Code が生成・要確認)
name = "my-sveltekit-app"
compatibility_date = "2025-11-01"
compatibility_flags = ["nodejs_compat"]
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "YOUR_D1_DATABASE_ID"
[[kv_namespaces]]
binding = "SESSION_KV"
id = "YOUR_KV_NAMESPACE_ID"
[vars]
PUBLIC_APP_NAME = "My SvelteKit App"
デプロイスクリプトの自動化
Claude Codeにデプロイスクリプトを作成させることで、マイグレーション → ビルド → デプロイの一連の流れを自動化できます。
# scripts/deploy.sh(Claude Code が生成)
#!/bin/bash
set -e
echo "🔄 DBマイグレーション実行中..."
npx drizzle-kit migrate --config=drizzle.config.ts
echo "🏗️ ビルド中..."
npm run build
echo "🚀 Cloudflare Workers にデプロイ中..."
npx wrangler deploy
echo "✅ デプロイ完了!"
第6章:Vitest × Playwright によるテスト戦略
Claude Code に先にテストを書かせる
テスト駆動開発(TDD)でClaude Codeを活用する方法として、実装前にテスト仕様を渡すアプローチが効果的です。
# Claude Code への指示(TDD アプローチ)
「CartStore の Vitest テストを先に作成してください。
仕様:
- addItem(product, quantity): カートに商品を追加、既存商品は数量を加算
- removeItem(productId): カートから商品を削除
- clearCart(): カートを空にする
- totalPrice: カートの合計金額($derived)
- itemCount: 商品の総数量($derived)
テストが完成したら、それを通過する実装コードも作成してください」
// src/lib/stores/cart.test.ts(Claude Code が生成)
import { describe, it, expect, beforeEach } from 'vitest';
import { createCartStore } from './cart.svelte';
describe('CartStore', () => {
let cart: ReturnType<typeof createCartStore>;
beforeEach(() => {
cart = createCartStore();
});
it('商品を追加できる', () => {
cart.addItem({ id: '1', name: 'テスト商品', price: 1000, imageUrl: '' }, 2);
expect(cart.items.length).toBe(1);
expect(cart.items[0].quantity).toBe(2);
});
it('同じ商品を追加すると数量が加算される', () => {
cart.addItem({ id: '1', name: 'テスト商品', price: 1000, imageUrl: '' }, 2);
cart.addItem({ id: '1', name: 'テスト商品', price: 1000, imageUrl: '' }, 3);
expect(cart.items.length).toBe(1);
expect(cart.items[0].quantity).toBe(5);
});
it('合計金額が正しく計算される', () => {
cart.addItem({ id: '1', name: '商品A', price: 1000, imageUrl: '' }, 2);
cart.addItem({ id: '2', name: '商品B', price: 500, imageUrl: '' }, 1);
expect(cart.totalPrice).toBe(2500);
});
it('商品を削除できる', () => {
cart.addItem({ id: '1', name: 'テスト商品', price: 1000, imageUrl: '' }, 1);
cart.removeItem('1');
expect(cart.items.length).toBe(0);
});
});
Playwright E2E テストの自動生成
認証フローや購入フローのE2Eテストも、Claude Codeに生成させることができます。
// tests/checkout.spec.ts(Claude Code が生成)
import { test, expect } from '@playwright/test';
test.describe('チェックアウトフロー', () => {
test.beforeEach(async ({ page }) => {
// テスト用ユーザーでログイン(test/fixtures/auth.ts から)
await page.goto('/login');
await page.fill('[data-testid="email"]', 'test@example.com');
await page.getByRole('button', { name: 'メールでログイン' }).click();
// メール確認省略(テスト環境ではシード済みセッションCookieを使用)
});
test('商品をカートに追加して購入できる', async ({ page }) => {
await page.goto('/products');
await page.getByTestId('product-card').first().getByRole('button', { name: 'カートに追加' }).click();
await page.goto('/cart');
await expect(page.getByTestId('cart-item')).toHaveCount(1);
await page.getByRole('button', { name: '購入手続きへ' }).click();
await expect(page).toHaveURL('/checkout');
});
});
第7章:よくあるエラーと Claude Code での対処法
エラー1:window is not defined(SSRエラー)
SvelteKitのSSR環境でブラウザAPIを使うと発生します。Claude Codeへの指示時に「SSR対応コードを書いてほしい」と添えるだけで防げます。
// ❌ エラーが発生するコード
const stored = window.localStorage.getItem('theme');
// ✅ Claude Code が生成するSSR対応コード
import { browser } from '$app/environment';
const stored = browser ? window.localStorage.getItem('theme') : null;
エラー2:Drizzle ORM の型エラー
Cloudflare D1でDrizzleを使う際、better-sqlite3ではなくdrizzle-orm/d1を使う必要があります。
// src/lib/server/db.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';
// Cloudflare Workers の env から D1 インスタンスを取得
export function createDb(d1: D1Database) {
return drizzle(d1, { schema });
}
// hooks.server.ts でセットアップ
export const handle: Handle = sequence(authHandle, async ({ event, resolve }) => {
event.locals.db = createDb(event.platform?.env?.DB);
return resolve(event);
});
エラー3:Auth.js のセッション型エラー
型定義の拡張を忘れるとTypeScriptエラーが発生します。
// src/app.d.ts に追加(Claude Code に忘れず指示)
declare global {
namespace App {
interface Locals {
auth: import('@auth/sveltekit').SvelteKitAuth['auth'];
db: ReturnType<typeof import('$lib/server/db').createDb>;
}
interface PageData {
session?: import('@auth/core/types').Session | null;
}
}
}
第8章:パフォーマンス最適化
画像最適化と遅延ロード
SvelteKitには@sveltejs/enhanced-imgパッケージによる画像最適化が用意されています。
<!-- ✅ Claude Code に指示するパターン -->
<script>
import { enhance } from '$app/forms';
</script>
<!-- enhanced:img で自動的にWebP変換・サイズ最適化 -->
<enhanced:img src="./product.png" alt="商品画像" class="w-full" />
ページプリロードの戦略的活用
<!-- リンクホバーでプリロード(Claude Code が自動追加) -->
<a href="/products/{product.id}" data-sveltekit-preload-data="hover">
{product.name}
</a>
Cloudflare Workers でのキャッシュ設定
// src/routes/products/+page.server.ts に追加
export const load: PageServerLoad = async ({ url, setHeaders }) => {
// 商品一覧は60秒キャッシュ
setHeaders({
'cache-control': 'public, max-age=60, s-maxage=60',
});
// ... データ取得処理
};
ここまでの要点
Claude Code × SvelteKitの組み合わせは、特に「型安全な実装をスピーディに」という要件に強みを発揮します。本記事で解説した主要なポイントをまとめます。
まず、CLAUDE.md の設計が品質の土台です。Svelte 5 Runesの使用を明示し、禁止事項を設定することで、Claude Codeが適切なコードを生成する確率が大幅に上がります。
次に、テスト先行アプローチは、Claude Codeの強みを最大化します。テスト仕様を先に渡すことで、実装の設計方針が自然と明確になり、品質の高いコードが生まれます。
最後に、Cloudflare Workers + D1 + Drizzle ORMのスタックは、Claude Codeが型安全なコードを生成しやすい組み合わせです。wrangler.tomlの設定も含めてClaude Codeに任せることで、インフラの設定ミスを減らせます。
SvelteKitフルスタック開発をさらに深めたい方には、Claude Code × Drizzle ORM × PostgreSQL 型安全データベース開発ガイドとClaude Code Playwright E2Eテスト自動化も合わせてご参照ください。