Use Cases

Desarrollo con Svelte y Claude Code

Aprenda sobre desarrollo con Svelte usando Claude Code. Incluye ejemplos practicos de codigo.

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公式サイトを参照してください。

#Claude Code #Svelte #SvelteKit #frontend #TypeScript