メインコンテンツまでスキップ

Cursor での Svelte および SvelteKit 開発: 完全ガイド

Cursor での Svelte 開発

Svelte はコンパイラベースの UI フレームワークであり、ブラウザに最小限の JavaScript を送信します。SvelteKit はそのフルスタックアプリケーションフレームワークです。Svelte 5 は runes を導入しました。これは以前の let ベースのリアクティビティよりも明示的で強力な新しいリアクティビティモデルです。Cursor は、VS Code 互換の基盤と統合された AI アシスタントにより、Svelte 開発に優れたエディタです。.svelte ファイルを理解し、豊富な IntelliSense を提供し、自然言語のプロンプトからコンポーネントを生成できます。

このガイドでは、Cursor での完全な Svelte 開発ワークフローをカバーします: ツールチェーンのセットアップ、Svelte 5 runes の習得、ファイルベースルーティングを使用した SvelteKit アプリの構築、stores による共有状態の管理、AI を活用したコンポーネントの生成とリファクタリング。

Cursor での Svelte 開発環境のセットアップ​

前提条件​

Svelte 5 と SvelteKit には Node.js 18 以上が必要です。インストールを確認してください:

node --version
npm --version

より新しいバージョンが必要な場合は、fnm または nvm を使用します:

fnm install 20
fnm use 20
ヒント

Cursor の統合ターミナル(Ctrl+`)は現在の作業ディレクトリを記憶するため、プロジェクトをスキャフォールドしてコンテキストを切り替えることなくすぐにファイルを開けます。

Svelte 拡張機能のインストール​

Cursor は言語サポートのために公式 Svelte 拡張機能に依存しています。拡張機能ビュー(Ctrl+Shift+X)からインストールします:

  1. "Svelte" を検索します。
  2. Svelte チームが公開している拡張機能をインストールします。
  3. プロンプトが表示されたら Cursor をリロードします。

この拡張機能は以下を提供します:

  • .svelte ファイルのシンタックスハイライト。
  • svelte-language-server による診断と型チェック。
  • Prettier と prettier-plugin-svelte パッケージによるフォーマット。
  • コンポーネントのインポートと props の IntelliSense。
警告

以前にコミュニティの Svelte 拡張機能を使用していた場合は、公式のものをインストールする前に無効にしてください。競合する拡張機能は重複した診断と信頼性の低いフォーマットを引き起こします。

SvelteKit プロジェクトのスキャフォールド​

公式 sv CLI を使用して新しい SvelteKit アプリケーションを作成します:

npx sv create my-svelte-app

インタラクティブなプロンプトでプリセットが提供されます:

? Which template would you like? SvelteKit minimal
? Add type checking with TypeScript? Yes, using TypeScript syntax
? Add ESLint for code linting? Yes
? Add Prettier for code formatting? Yes
? Add Playwright for browser testing? No
? Add Vitest for unit testing? Yes

プロジェクトを Cursor で開きます:

cd my-svelte-app
cursor .
npm install
npm run dev

開発サーバーは http://localhost:5173 で起動します。

ヒント

プロジェクトを作り直すことなく、後から機能を追加できます。sv add コマンドは、Tailwind CSS、Storybook、Drizzle などの統合を既存のアプリにインストールします:

npx sv add tailwindcss
npx sv add storybook

Svelte 5 Runes の習得​

Runes とは?​

Runes は Svelte 5 で導入された特別なコンパイラ認識関数です。Svelte 4 の暗黙的リアクティビティを、明示的で予測可能なモデルに置き換えます。コアとなる runes は以下の通りです:

  • $state — リアクティブな状態を宣言します。
  • $derived — 他のリアクティブな状態から計算された値を宣言します。
  • $effect — 依存関係が変更されたときに副作用を実行します。
  • $props — コンポーネント props を宣言します。
  • $bindable — prop を双方向バインド可能としてマークします。

$state で状態を宣言​

<script lang="ts">
let count = $state(0);
let user = $state({ name: 'Ada', age: 36 });

function increment() {
count++;
}
</script>

<button onclick={increment}>
Count: {count}
</button>
<p>{user.name} is {user.age}</p>
ヒント

Svelte 5 では、イベントハンドラは古い on:click の代わりに onclick 属性(小文字、on: プレフィックスなし)を使用します。新しい構文は標準的な DOM 属性に近く、TypeScript との相性も良いです。

$derived で派生値を作成​

<script lang="ts">
let price = $state(100);
let quantity = $state(2);
let total = $derived(price * quantity);
let formatted = $derived(
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total)
);
</script>

<p>Price: $ {price}</p>
<p>Quantity: {quantity}</p>
<p>Total: {formatted}</p>

$effect で副作用を処理​

<script lang="ts">
let theme = $state<'light' | 'dark'>('light');

$effect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
});
</script>

<button onclick={() => (theme = theme === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
警告

$effect は DOM が更新された後に実行されます。レンダリングに影響する値の計算には使用しないでください。代わりに $derived を使用します。Effect は外部システム(API、localStorage、サードパーティライブラリ)との同期に使用します。

$props でコンポーネント Props を宣言​

<script lang="ts">
interface Props {
title: string;
count?: number;
onIncrement?: (value: number) => void;
}

let { title, count = 0, onIncrement }: Props = $props();

function handleClick() {
count++;
onIncrement?.(count);
}
</script>

<h2>{title}</h2>
<button onclick={handleClick}>Clicked {count} times</button>

$bindable で双方向バインディング​

<!-- TextInput.svelte -->
<script lang="ts">
interface Props {
value?: string;
}
let { value = $bindable('') }: Props = $props();
</script>

<input bind:value />

親コンポーネントでの使用:

<script lang="ts">
import TextInput from './TextInput.svelte';
let name = $state('');
</script>

<TextInput bind:value={name} />
<p>Hello, {name}!</p>
ヒント

$bindable は Svelte 5 における export let + bind:value パターンの代替です。双方向バインディングの契約を子コンポーネントの型シグネチャで明示的にします。

SvelteKit アプリケーションの構築​

ファイルベースルーティング​

SvelteKit は src/routes ディレクトリ内でファイルベースルーティングを使用します。各 +page.svelte ファイルがページになり、+layout.svelte ファイルがネストされたページをラップします。

src/routes/
├── +layout.svelte
├── +page.svelte → /
├── about/
│ └── +page.svelte → /about
├── blog/
│ ├── +page.svelte → /blog
│ └── [slug]/
│ └── +page.svelte → /blog/:slug
└── +layout.svelte

+page.server.ts でデータをロード​

// src/routes/blog/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

interface Post {
slug: string;
title: string;
body: string;
}

export const load: PageServerLoad = async ({ params }) => {
const response = await fetch(`https://api.example.com/posts/${params.slug}`);
if (!response.ok) {
throw error(404, 'Post not found');
}
const post: Post = await response.json();
return { post };
};

ページでデータを消費:

<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>

<article>
<h1>{data.post.title}</h1>
<p>{data.post.body}</p>
</article>
ヒント

$types モジュールは SvelteKit によって自動生成されます。完全に型付けされた load 関数と data props を提供します。Cursor の IntelliSense はこれらの型を自動的に読み取るため、data.post.title の自動補完が得られます。

フォームアクション​

SvelteKit は +page.server.ts で定義された actions でフォーム送信を処理します:

// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions: Actions = {
default: async ({ request, cookies }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');

if (!email || !password) {
return fail(400, { message: 'Missing credentials' });
}

const token = await authenticate(String(email), String(password));
if (!token) {
return fail(401, { message: 'Invalid credentials' });
}

cookies.set('session', token, { path: '/' });
throw redirect(303, '/dashboard');
},
};

対応するページは enhance アクションでプログレッシブエンハンスメントを実装:

<!-- src/routes/login/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageData } from './$types';
let { form }: { form: PageData } = $props();
</script>

<form method="POST" use:enhance>
<label>Email <input name="email" type="email" /></label>
<label>Password <input name="password" type="password" /></label>
{#if form?.message}
<p class="error">{form.message}</p>
{/if}
<button type="submit">Log in</button>
</form>
警告

サーバー側の +page.server.ts ファイルはサーバー上でのみ実行されます。ブラウザ専用 API(localStorage など)をインポートしないでください。また、シークレットを +page.ts(ユニバーサル load)に置かないでください。これもブラウザで実行される可能性があります。

レイアウトとレイアウトグループ​

<!-- src/routes/+layout.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
import Nav from '../components/Nav.svelte';

let { children }: { children: Snippet } = $props();
</script>

<div class="app">
<Nav />
<main>{@render children()}</main>
</div>

Svelte 5 では、children snippet が非推奨の <slot /> 構文に代わります。

Stores で共有状態を管理​

Svelte Stores の基本​

runes があっても、Svelte stores はコンポーネント間の状態に有用です。store は subscribe メソッドを持つ任意のオブジェクトです。

// lib/stores/user.ts
import { writable } from 'svelte/store';

interface User {
id: string;
name: string;
}

export const currentUser = writable<User | null>(null);

$ プレフィックスで store を消費:

<script lang="ts">
import { currentUser } from '$lib/stores/user';
</script>

{#if $currentUser}
<p>Welcome, {$currentUser.name}</p>
{:else}
<p>Please log in.</p>
{/if}

Readable と Derived Stores​

import { readable, derived } from 'svelte/store';

export const time = readable(new Date(), (set) => {
const interval = setInterval(() => set(new Date()), 1000);
return () => clearInterval(interval);
});

export const formattedTime = derived(time, ($time) =>
$time.toLocaleTimeString('en-US')
);

Runes と Stores の組み合わせ​

$effect 内で stores を使用して、レガシーコードと runes をブリッジできます:

<script lang="ts">
import { currentUser } from '$lib/stores/user';
import { get } from 'svelte/store';

let userSnapshot = $state(get(currentUser));

$effect(() => {
const unsubscribe = currentUser.subscribe((value) => {
userSnapshot = value;
});
return unsubscribe;
});
</script>
ヒント

新規プロジェクトでは、$state フィールドを持つクラスと Svelte context API を使用した runes ベースの共有状態パターンを推奨します。これにより store のボイラープレートを避けつつリアクティビティを維持できます:

// lib/state/counter.svelte.ts
export class CounterState {
count = $state(0);
increment() {
this.count++;
}
}

AI 支援 Svelte コンポーネント生成​

プロンプトからコンポーネントを生成​

Cursor の AI は runes を使用する Svelte 5 コンポーネントの生成に優れています。チャットを開く(Ctrl+Shift+P → "Cursor: New Chat")かインライン編集(Ctrl+K)を使用して、必要なものを記述します:

Create a Svelte 5 component (script lang="ts") for an accessible accordion.
It should accept an items prop: array of { id, title, content }. Use runes
for state (which panel is open). Include keyboard navigation (arrow keys)
and scoped styles.

Cursor は完全な .svelte ファイルを生成します。出力を確認し、反復します — allowMultiple prop やアニメーショントランジションの追加を依頼します。

Svelte 4 から Svelte 5 への移行​

古いコードベースをメンテナンスしている場合、Cursor はコンポーネントを runes 構文に移行できます。ファイルをハイライトし、Ctrl+K を押して、プロンプトを入力します:

Migrate this Svelte 4 component to Svelte 5 runes syntax. Replace `let`
reactivity with $state, `$:` with $derived/$effect, `export let` with $props,
and `createEventDispatcher` with callback props. Preserve all behavior.
警告

Runes の移行は微妙な方法でリアクティビティのタイミングを変更します。リファクタリング後、テストスイートを実行し、ライフサイクルフックやウォッチャーを使用するコンポーネントを手動で確認してください。$effect は古い afterUpdate とは異なる条件で発火します。

エンドツーエンドテストの生成​

Playwright と AI 生成テストをペアにします:

// tests/accordion.spec.ts
import { test, expect } from '@playwright/test';

test('accordion expands on click', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Section 1' }).click();
await expect(page.getByText('Content of section 1')).toBeVisible();
});

Cursor にプロンプト: "Generate Playwright tests for Accordion.svelte covering expand/collapse and keyboard navigation."

Svelte プロジェクト用の Cursor 設定​

ワークスペース設定​

.vscode/settings.json を作成して、チーム全体でフォーマットを標準化します:

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "svelte.svelte-vscode",
"svelte.plugin.svelte.format.config.singleQuote": true,
"svelte.plugin.svelte.format.config.semi": true,
"svelte.plugin.svelte.note-new-transformation": false,
"typescript.tsdk": "node_modules/typescript/lib"
}

Prettier と Svelte プラグイン​

npm install -D prettier prettier-plugin-svelte

.prettierrc:

{
"singleQuote": true,
"semi": true,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": { "parser": "svelte" }
}
]
}
ヒント

Prettier は <script> と <style> ブロックをフォーマットしますが、ディレクティブを壊さないよう Svelte テンプレートマークアップは意図的に処理しません。テンプレートのフォーマットには、Svelte 拡張機能の組み込みフォーマッタに依存してください。

ESLint 設定​

// eslint.config.js
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import tsParser from 'typescript-eslint';

export default [
js.configs.recommended,
...svelte.configs['flat/recommended'],
{
languageOptions: {
parserOptions: {
extraFileExtensions: ['.svelte'],
},
},
},
];

デバッグとパフォーマンス​

Svelte DevTools​

Svelte DevTools ブラウザ拡張機能をインストールして、コンポーネント階層と状態を確認します。Cursor がエディタをホストしますが、ブラウザ DevTools はランタイム値を表示し、runes のデバッグに不可欠です。

本番環境での Source Maps​

SvelteKit はデフォルトで source maps を生成します。svelte.config.js で確認:

import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
};

export default config;
警告

サーバーレスプラットフォームにデプロイする場合、adapter が source maps を正しく処理することを確認してください。adapter-node と adapter-vercel は動作が異なります。source maps を本番環境に送信する前に、adapter のドキュメントを確認してください。

Bundle 分析​

rollup-plugin-visualizer を追加して bundle サイズを確認:

// vite.config.ts
import { sveltekit } from '@sveltejs/vite-plugin-sveltekit';
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
plugins: [
sveltekit(),
visualizer({
filename: 'stats.html',
open: true,
gzipSize: true,
}),
],
});

Cursor での Svelte 開発のベストプラクティス​

プロジェクト構造​

src/
├── lib/
│ ├── components/
│ │ ├── Accordion.svelte
│ │ └── TextInput.svelte
│ ├── stores/
│ │ └── user.ts
│ ├── state/
│ │ └── counter.svelte.ts
│ └── utils/
├── routes/
│ ├── +layout.svelte
│ └── +page.svelte
└── app.html

Runes ファイルの命名規則​

Svelte 5 は .svelte.ts(および .svelte.js)拡張子を導入し、コンポーネント外で runes を使用するモジュールに使用します。共有状態ファイルにそれに応じて名前を付けます:

// src/lib/state/cart.svelte.ts
export class CartState {
items = $state<{ id: number; qty: number }[]>([]);

get total() {
return this.items.reduce((sum, i) => sum + i.qty, 0);
}

add(id: number, qty = 1) {
this.items.push({ id, qty });
}
}
ヒント

.svelte.ts サフィックスは Svelte コンパイラに通常の TypeScript モジュール内で runes を処理するよう指示します。これがないと、$state は未定義関数として扱われ、ビルドが失敗します。

デフォルトのアクセシビリティ​

Svelte のコンパイラは欠落している alt、aria-*、ラベル関連付けについて警告します。CI でこれらの警告をエラーとして扱います:

// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const config = {
preprocess: vitePreprocess(),
kit: { adapter: adapter() },
compilerOptions: {
accessors: false,
},
};

export default config;

よくある落とし穴と回避方法​

レガシーイベント構文の使用​

Svelte 5 は on:click を非推奨とし、onclick を採用しています。移行期間中は両方を混在させることができますが、警告が表示されます。新しい構文に統一してください:

<!-- Svelte 4(非推奨) -->
<button on:click={handler}>Click</button>

<!-- Svelte 5(推奨) -->
<button onclick={handler}>Click</button>

Props の変更​

Props はデフォルトで読み取り専用です。双方向バインディングには $bindable を使用するか、コールバックを発行します:

<script lang="ts">
interface Props {
value: string;
onChange?: (v: string) => void;
}
let { value, onChange }: Props = $props();

function handleInput(e: Event) {
const target = e.target as HTMLInputElement;
onChange?.(target.value);
}
</script>

<input value={value} oninput={handleInput} />

$effect の過剰使用​

Effect は外部システムと同期するものであり、レンダリング値を計算するものではありません。$effect を使ってテンプレートで使用する変数を更新している場合は、$derived に切り替えてください:

<script lang="ts">
let a = $state(1);
let b = $state(2);

// 間違い — 派生値に effect を使用
// let sum = $state(0);
// $effect(() => { sum = a + b; });

// 正しい — 派生
let sum = $derived(a + b);
</script>
警告

自分自身に依存する effect は無限ループを作成する可能性があります。Svelte コンパイラは一部のケースを検出できますが、effect の依存関係が有限で終了することを常に確認してください。

まとめ​

Cursor は Svelte および SvelteKit 開発のための強力な環境です。公式 Svelte 拡張機能は型安全なオーサリングとフォーマットを提供し、Svelte 5 runes は明示的で予測可能なリアクティビティをもたらし、SvelteKit のファイルベースルーティングと型付けされた load 関数はフルスタック作業を加速します。これらのツールを Cursor の AI アシスタントと組み合わせてコンポーネントの生成、リファクタリング、テストスキャフォールドを行うことで、現代的な Svelte アプリケーションを効率的に構築できます。sv create から始め、Prettier と ESLint の設定を固定し、runes と AI に繰り返しのスキャフォールド作業を任せて、あなたは洗練されたユーザー体験の提供に集中してください。