Développement Svelte avec Claude Code
Découvrez développement Svelte avec Claude Code. Conseils pratiques et exemples de code inclus.
Svelte開発でClaude Codeを使うメリット
Svelteはコンパイラベースのフレームワークで、少ないボイラープレートで高性能なUIを構築できます。Claude Codeはリアクティブ宣言やSvelteKit固有のルーティング・データロードのパターンを理解し、効率的なコードを生成できます。
Svelteコンポーネントの生成
> Svelteでモーダル付きの画像ギャラリーコンポーネントを作成して。
> グリッド表示、クリックで拡大、キーボードナビゲーション対応で。
<!-- ImageGallery.svelte -->
<script lang="ts">
interface Image {
id: string;
src: string;
alt: string;
thumbnail: string;
}
export let images: Image[] = [];
let selectedIndex = -1;
let isModalOpen = false;
$: selectedImage = selectedIndex >= 0 ? images[selectedIndex] : null;
function openModal(index: number) {
selectedIndex = index;
isModalOpen = true;
}
function closeModal() {
isModalOpen = false;
selectedIndex = -1;
}
function navigate(direction: number) {
selectedIndex = (selectedIndex + direction + images.length) % images.length;
}
function handleKeydown(e: KeyboardEvent) {
if (!isModalOpen) return;
switch (e.key) {
case 'Escape': closeModal(); break;
case 'ArrowLeft': navigate(-1); break;
case 'ArrowRight': navigate(1); break;
}
}
</script>
<svelte:window on:keydown={handleKeydown} />
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
{#each images as image, i}
<button
class="overflow-hidden rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
on:click={() => openModal(i)}
>
<img
src={image.thumbnail}
alt={image.alt}
class="h-48 w-full object-cover transition-transform hover:scale-105"
loading="lazy"
/>
</button>
{/each}
</div>
{#if isModalOpen && selectedImage}
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80"
on:click={closeModal}
role="dialog"
aria-modal="true"
aria-label="画像拡大表示"
>
<div class="relative max-h-[90vh] max-w-[90vw]" on:click|stopPropagation>
<img
src={selectedImage.src}
alt={selectedImage.alt}
class="max-h-[90vh] max-w-[90vw] object-contain"
/>
<button
class="absolute -left-12 top-1/2 -translate-y-1/2 rounded-full bg-white/20 p-2 text-white hover:bg-white/40"
on:click={() => navigate(-1)}
aria-label="前の画像"
>
←
</button>
<button
class="absolute -right-12 top-1/2 -translate-y-1/2 rounded-full bg-white/20 p-2 text-white hover:bg-white/40"
on:click={() => navigate(1)}
aria-label="次の画像"
>
→
</button>
<button
class="absolute -top-10 right-0 text-white hover:text-gray-300"
on:click={closeModal}
aria-label="Close"
>
Close
</button>
</div>
<p class="absolute bottom-4 text-center text-white">
{selectedIndex + 1} / {images.length}
</p>
</div>
{/if}
Svelteストアの設計
> Svelteのストアでテーマとユーザー設定を管理して。
> localStorage永続化付きで。
// stores/settings.ts
import { writable, derived } from 'svelte/store';
import { browser } from '$app/environment';
interface UserSettings {
theme: 'light' | 'dark' | 'system';
language: 'ja' | 'en';
fontSize: 'sm' | 'md' | 'lg';
sidebarCollapsed: boolean;
}
const defaultSettings: UserSettings = {
theme: 'system',
language: 'ja',
fontSize: 'md',
sidebarCollapsed: false,
};
function createSettingsStore() {
const initial = browser
? JSON.parse(localStorage.getItem('settings') || 'null') || defaultSettings
: defaultSettings;
const { subscribe, set, update } = writable<UserSettings>(initial);
return {
subscribe,
update(partial: Partial<UserSettings>) {
update(current => {
const next = { ...current, ...partial };
if (browser) localStorage.setItem('settings', JSON.stringify(next));
return next;
});
},
reset() {
set(defaultSettings);
if (browser) localStorage.removeItem('settings');
},
};
}
export const settings = createSettingsStore();
// 派生ストア:実際に適用されるテーマ
export const effectiveTheme = derived(settings, ($settings) => {
if ($settings.theme !== 'system') return $settings.theme;
if (!browser) return 'light';
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
});
SvelteKitのサーバーサイド機能
> SvelteKitでブログのデータロードとフォームアクションを実装して。
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad, Actions } from './$types';
import { error, fail } from '@sveltejs/kit';
import { prisma } from '$lib/server/db';
export const load: PageServerLoad = async ({ params }) => {
const post = await prisma.post.findUnique({
where: { slug: params.slug },
include: {
author: { select: { name: true, avatar: true } },
comments: {
include: { author: { select: { name: true } } },
orderBy: { createdAt: 'desc' },
},
},
});
if (!post) throw error(404, 'Post not found');
return { post };
};
export const actions: Actions = {
comment: async ({ request, params, locals }) => {
const session = await locals.getSession();
if (!session) return fail(401, { message: 'ログインが必要です' });
const formData = await request.formData();
const body = formData.get('body')?.toString();
if (!body || body.length < 2) {
return fail(400, { message: 'コメントは2文字以上入力してください', body });
}
await prisma.comment.create({
data: {
body,
postSlug: params.slug,
authorId: session.user.id,
},
});
return { success: true };
},
};
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData, ActionData } from './$types';
import { enhance } from '$app/forms';
export let data: PageData;
export let form: ActionData;
</script>
<article class="prose mx-auto max-w-3xl">
<h1>{data.post.title}</h1>
<p class="text-gray-500">
{data.post.author.name} - {new Date(data.post.createdAt).toLocaleDateString('en-US')}
</p>
{@html data.post.content}
</article>
<section class="mx-auto mt-12 max-w-3xl">
<h2 class="text-xl font-bold">コメント ({data.post.comments.length})</h2>
<form method="POST" action="?/comment" use:enhance>
<textarea
name="body"
rows="3"
class="mt-4 w-full rounded border p-3"
placeholder="コメントを入力..."
value={form?.body ?? ''}
/>
{#if form?.message}
<p class="text-sm text-red-600">{form.message}</p>
{/if}
<button type="submit" class="mt-2 rounded bg-blue-600 px-4 py-2 text-white">
送信
</button>
</form>
{#each data.post.comments as comment}
<div class="mt-4 rounded border p-4">
<p class="font-medium">{comment.author.name}</p>
<p class="mt-1 text-gray-700">{comment.body}</p>
</div>
{/each}
</section>
テスト
import { render, fireEvent } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';
import ImageGallery from './ImageGallery.svelte';
describe('ImageGallery', () => {
const images = [
{ id: '1', src: '/img1.jpg', alt: 'Image 1', thumbnail: '/thumb1.jpg' },
{ id: '2', src: '/img2.jpg', alt: 'Image 2', thumbnail: '/thumb2.jpg' },
];
it('should render all images', () => {
const { getAllByRole } = render(ImageGallery, { props: { images } });
expect(getAllByRole('button')).toHaveLength(2);
});
it('should open modal on click', async () => {
const { getAllByRole, getByRole } = render(ImageGallery, { props: { images } });
await fireEvent.click(getAllByRole('button')[0]);
expect(getByRole('dialog')).toBeTruthy();
});
});
Summary
Claude Codeを使えば、Svelte / SvelteKitのコンポーネント、ストア、サーバーサイドロジックを効率的に開発できます。Svelteのリアクティブ宣言やSvelteKit固有のload関数・フォームアクションもClaude Codeは正確に扱えます。プロンプトの工夫で品質を高めるコツはプロンプトテクニック完全ガイドを参照してください。フレームワーク比較に興味がある方はClaude Code vs GitHub Copilotもご覧ください。
Claude Codeの詳細はAnthropic公式ドキュメントをご覧ください。SvelteKitの公式ガイドはSvelteKit公式サイトを参照してください。
Related Posts
Comment booster vos projets personnels avec Claude Code [Avec exemples]
Apprenez à accélérer considérablement vos projets de développement personnels avec Claude Code. Inclut des exemples concrets et un workflow pratique de l'idée au déploiement.
Comment automatiser le refactoring avec Claude Code
Apprenez à automatiser efficacement le refactoring de code avec Claude Code. Inclut des prompts pratiques et des patterns de refactoring concrets pour des projets réels.
Guide complet de configuration CORS avec Claude Code
Découvrez le guide complet de configuration CORS avec Claude Code. Conseils pratiques et exemples de code inclus.