Svelte & SvelteKit 完全ガイド - コンパイラベースUIフレームワークの全貌

Svelte & SvelteKit 完全ガイド - コンパイラベースUIフレームワークの全貌

作成日:
更新日:

Svelteとは

Svelteは、Rich Harris氏が開発した革新的なUIフレームワークです。2016年に登場し、React、Vue、Angularなどの従来のフレームワークとは根本的に異なるアプローチを採用しています。Svelteの最大の特徴は、コンパイル時に最適化されたバニラJavaScriptを生成することです。

Svelte is a UI framework that uses a compiler to let you write breathtakingly concise components that do minimal work in the browser — Svelte公式サイト

Svelteは「フレームワークではなくコンパイラ」という斬新なコンセプトにより、Stack Overflow Developer Survey や State of JavaScript などの調査で常に開発者満足度トップクラスを維持しています。New York Times、Apple、Spotify、Square など多くの企業でも採用されています。


従来のフレームワークとの違い

従来のフレームワークとSvelteの根本的な違いを理解することが重要です。

特徴React/VueSvelte
実行方式ランタイムで仮想DOM差分計算コンパイル時にコード生成
バンドルサイズフレームワークコード含む最小限のコードのみ
パフォーマンス仮想DOM経由DOM直接操作
学習コストフレームワーク固有APIHTML/CSS/JSに近い構文

Svelteの特徴

Svelteには従来のフレームワークにはない独自の特徴がいくつかあります。これらを理解することで、なぜSvelteが高いパフォーマンスと開発者体験を実現できるのかが分かります。


1. コンパイラベースアプローチ

Svelteの最大の革新は、ビルド時にコンポーネントを効率的なJavaScriptに変換することです。

ReactやVueは、ブラウザ上で動作する「ランタイム」を含んでおり、アプリケーションの実行時に仮想DOMの差分計算を行います。一方、Svelteはコンパイル時にこの計算を済ませてしまうため、ランタイムがほぼ不要です。

この結果:

  • バンドルサイズが劇的に小さくなる(フレームワークのコードがほとんど含まれない)
  • 実行時パフォーマンスが優れる(仮想DOM差分計算のオーバーヘッドがない)
  • 初期ロードが高速(読み込むJavaScriptが少ない)
Svelte
<script>
  let count = 0;
</script>
 
<button on:click={() => count++}>
  Clicked: {count}
</button>

このシンプルなコードがコンパイルされると、DOM操作を直接行う最適化されたJavaScriptが生成されます。余計な抽象化レイヤーがないため、デバッグも容易です。


2. 簡潔な構文

Svelteのコンポーネントは、HTML、CSS、JavaScriptを1つの.svelteファイルにまとめて記述します。これはVueのSFC(Single File Component)に似ていますが、よりシンプルで直感的です。

Svelte
<script>
  let name = 'World';
</script>
 
<h1>Hello {name}!</h1>
 
<style>
  h1 {
    color: #ff3e00;
    font-family: 'Segoe UI', sans-serif;
  }
</style>

CSSは自動的にスコープ化されるため、スタイルの衝突を心配する必要がありません。CSS ModulesやCSS-in-JSライブラリを別途導入する必要はなく、普通のCSSをそのまま書けます。


3. リアクティビティ

リアクティビティ(反応性)とは、データが変更されたときにUIが自動的に更新される仕組みです。Svelteのリアクティビティは非常にシンプルで直感的です。

Svelte 4以前では、単純な代入文でリアクティビティが実現されていました。

Svelte
<script>
  let count = 0;
  
  // $: はリアクティブ宣言(派生値)
  $: doubled = count * 2;
  
  // $: はリアクティブステートメント(副作用)
  $: console.log(`count is ${count}`);
</script>
 
<p>{count} x 2 = {doubled}</p>
<button on:click={() => count++}>+1</button>

Svelte 5の新機能:Runes

2024年にリリースされたSvelte 5では、**Runes(ルーン)**という新しいリアクティビティシステムが導入されました。これはSvelteの歴史上最大の変更であり、より明示的で予測可能なリアクティビティを提供します。

Runesは$で始まる特別な関数群で、コンパイラに「この値はリアクティブである」ことを伝えます。従来の$:構文よりもTypeScriptとの相性が良く、大規模なアプリケーションでも保守しやすくなります。


$state - リアクティブな状態

$stateは、リアクティブな状態を作成する最も基本的なRuneです。

Svelte
<script>
  let count = $state(0);
</script>
 
<button onclick={() => count++}>
  Clicked: {count}
</button>

$stateは、値が変更されたときにUIを自動更新するリアクティブな状態を作成します。ReactのuseStateやVueのrefに相当しますが、より簡潔に記述できます。


$derived - 派生値

$derivedは、他のリアクティブ値から計算される値を定義します。VueのcomputedやReactのuseMemoに相当します。

Svelte
<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
</script>
 
<p>{count} x 2 = {doubled}</p>

$derivedは、依存する値が変更されると自動的に再計算されます。明示的に依存関係を指定する必要はなく、使用している値を自動的に追跡します。


$effect - 副作用

$effectは、リアクティブ値が変更されたときに副作用(side effect)を実行するためのRuneです。API呼び出し、DOM操作、ログ出力などに使用します。

Svelte
<script>
  let count = $state(0);
  
  $effect(() => {
    console.log(`count changed to ${count}`);
    // クリーンアップ関数を返すことも可能
    return () => {
      console.log('cleanup');
    };
  });
</script>

$effectは、ReactのuseEffectに相当しますが、依存配列を手動で指定する必要がありません。関数内で使用しているリアクティブ値を自動的に追跡します。クリーンアップ関数を返すことで、コンポーネントのアンマウント時の処理も定義できます。


$props - コンポーネントのProps

$propsは、親コンポーネントから渡されるプロパティを受け取るためのRuneです。デフォルト値の設定も簡単です。

Svelte
<!-- 子コンポーネント: Greeting.svelte -->
<script>
  let { name, greeting = 'Hello' } = $props();
</script>
 
<p>{greeting}, {name}!</p>
Svelte
<!-- 親コンポーネント: App.svelte -->
<script>
  import Greeting from './Greeting.svelte';
</script>
 
<Greeting name="Svelte" />
<Greeting name="World" greeting="Hi" />

Svelteのテンプレート構文

Svelteは、HTMLに近い直感的なテンプレート構文を提供します。条件分岐、ループ、非同期処理などをテンプレート内で簡潔に記述できます。

これらの構文は{#...}で始まり、{/...}で終わるブロック形式です。JSXとは異なり、JavaScript式をそのまま埋め込むのではなく、専用の構文が用意されています。


条件分岐 {#if}

{#if}ブロックで条件に基づいてコンテンツを表示・非表示にできます。

Svelte
<script>
  let loggedIn = $state(false);
</script>
 
{#if loggedIn}
  <button onclick={() => loggedIn = false}>Log out</button>
{:else}
  <button onclick={() => loggedIn = true}>Log in</button>
{/if}

ループ {#each}

{#each}ブロックで配列を繰り返し処理できます。Reactのmap()に相当しますが、より読みやすい構文です。

Svelte
<script>
  let items = $state([
    { id: 1, name: 'Apple' },
    { id: 2, name: 'Banana' },
    { id: 3, name: 'Cherry' }
  ]);
</script>
 
<ul>
  {#each items as item (item.id)}
    <li>{item.name}</li>
  {/each}
</ul>

(item.id)は各要素を識別するキーで、効率的な更新を可能にします。リストの並べ替えや要素の追加・削除時に、Svelteがどの要素を再利用できるかを判断するために使用されます。


非同期処理 {#await}

{#await}ブロックは、Promiseの状態(pending、resolved、rejected)に応じて異なるコンテンツを表示できます。これはSvelte独自の機能で、非同期処理のUIを非常に簡潔に記述できます。

Svelte
<script>
  async function fetchUser() {
    const res = await fetch('/api/user');
    return res.json();
  }
  
  let promise = fetchUser();
</script>
 
{#await promise}
  <p>Loading...</p>
{:then user}
  <p>Welcome, {user.name}!</p>
{:catch error}
  <p>Error: {error.message}</p>
{/await}

バインディングとディレクティブ

Svelteは、フォーム要素との双方向バインディングや条件付きスタイリングのための便利なディレクティブを提供しています。これにより、イベントリスナーを手動で設定する必要がなくなり、コードが大幅に簡潔になります。


bind: ディレクティブ(双方向バインディング)

bind:ディレクティブを使うと、フォーム要素の値とJavaScript変数を自動的に同期できます。

Svelte
<script>
  let name = $state('');
  let agreed = $state(false);
  let flavor = $state('vanilla');
</script>
 
<!-- テキスト入力 -->
<input type="text" bind:value={name} />
<p>Hello, {name || 'stranger'}!</p>
 
<!-- チェックボックス -->
<label>
  <input type="checkbox" bind:checked={agreed} />
  I agree to the terms
</label>
 
<!-- セレクトボックス -->
<select bind:value={flavor}>
  <option value="vanilla">Vanilla</option>
  <option value="chocolate">Chocolate</option>
  <option value="strawberry">Strawberry</option>
</select>

class: と style: ディレクティブ

class:style:ディレクティブを使うと、条件付きでクラスやスタイルを適用できます。三項演算子や複雑な式を使う必要がなく、非常に読みやすいコードになります。

Svelte
<script>
  let active = $state(true);
  let color = $state('#ff3e00');
</script>
 
<!-- クラスの条件付き適用 -->
<div class:active={active}>
  This div is {active ? 'active' : 'inactive'}
</div>
 
<!-- インラインスタイル -->
<div style:color={color} style:font-weight="bold">
  Styled text
</div>
 
<style>
  .active {
    background-color: #ffede5;
    border: 2px solid #ff3e00;
  }
</style>

class:active={active}は、activetrueのときだけactiveクラスを追加します。変数名とクラス名が同じ場合はclass:activeと短縮できます。

SvelteKitとは

SvelteKitは、SvelteのためのフルスタックWebアプリケーションフレームワークです。Next.js(React)やNuxt.js(Vue)に相当する存在で、Svelteでアプリケーションを構築する際の公式推奨フレームワークです。

SvelteKitは、Viteをビルドツールとして使用し、以下の機能を提供します:

機能説明
ファイルベースルーティングsrc/routes/のディレクトリ構造がそのままURLに
サーバーサイドレンダリング(SSR)SEOに強く、初期表示が高速
静的サイト生成(SSG)ビルド時にHTMLを生成
APIルートバックエンドAPIを同じプロジェクトで作成
データローディングサーバーでのデータ取得を統一的に処理
フォームアクションプログレッシブエンハンスメントに対応したフォーム処理

プロジェクトの作成

SvelteKitプロジェクトはsvコマンド(旧create-svelte)で簡単に作成できます。

Shell
# プロジェクト作成
npx sv create my-app
 
# ディレクトリに移動
cd my-app
 
# 依存関係インストール
npm install
 
# 開発サーバー起動
npm run dev

ディレクトリ構造

構造
my-app/
├── src/
│   ├── lib/           # 再利用可能なコンポーネント・ユーティリティ
│   │   └── components/
│   ├── routes/        # ページルート
│   │   ├── +page.svelte
│   │   ├── +layout.svelte
│   │   └── blog/
│   │       └── [slug]/
│   │           └── +page.svelte
│   ├── app.html       # HTMLテンプレート
│   └── app.css        # グローバルCSS
├── static/            # 静的ファイル
├── svelte.config.js   # Svelte設定
└── vite.config.js     # Vite設定

SvelteKitのルーティング

SvelteKitはファイルベースルーティングを採用しています。src/routes/ディレクトリ内のファイル構造がそのままURLにマッピングされるため、ルーティング設定ファイルを書く必要がありません。

ファイル名には特別な意味があります:

  • +page.svelte - ページのUI
  • +page.js / +page.server.js - データローディング
  • +layout.svelte - 共通レイアウト
  • +server.js - APIエンドポイント

基本的なページ

src/routes/ディレクトリ内のファイル構造がそのままURLになります。

Svelte
<!-- src/routes/+page.svelte -->
<h1>Home Page</h1>
<p>Welcome to my SvelteKit app!</p>
Svelte
<!-- src/routes/about/+page.svelte -->
<h1>About</h1>
<p>This is the about page.</p>

動的ルート

[param]形式でURLパラメータを受け取れます。

Svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  let { data } = $props();
</script>
 
<article>
  <h1>{data.post.title}</h1>
  <div>{@html data.post.content}</div>
</article>

レイアウト

+layout.svelteで複数ページに共通のレイアウトを定義します。

Svelte
<!-- src/routes/+layout.svelte -->
<script>
  import Header from '$lib/components/Header.svelte';
  import Footer from '$lib/components/Footer.svelte';
  
  let { children } = $props();
</script>
 
<Header />
<main>
  {@render children()}
</main>
<Footer />
 
<style>
  main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem;
  }
</style>

データローディング

SvelteKitのデータローディングは、コンポーネントとデータ取得ロジックを分離する強力な仕組みです。サーバーサイドでのデータ取得、キャッシング、エラーハンドリングなどを統一的に扱えます。


load関数

SvelteKitでは、+page.js(または+page.server.js)でload関数を定義してデータを取得します。この関数はページがレンダリングされる前に実行され、取得したデータはページコンポーネントで使用できます。

JavaScript
// src/routes/blog/[slug]/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ params, fetch }) {
  const response = await fetch(`/api/posts/${params.slug}`);
  
  if (!response.ok) {
    throw error(404, 'Post not found');
  }
  
  const post = await response.json();
  
  return {
    post
  };
}

サーバー専用のデータ取得

データベースへの直接アクセスや、機密情報(APIキーなど)を使用する場合は+page.server.jsを使用します。このファイル内のコードはサーバー上でのみ実行され、クライアントには一切送信されません。

JavaScript
// src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
import * as db from '$lib/server/database';
 
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
  const post = await db.getPost(params.slug);
  
  if (!post) {
    error(404, 'Post not found');
  }
  
  return {
    post
  };
}

ページでのデータ使用

Svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  let { data } = $props();
</script>
 
<svelte:head>
  <title>{data.post.title}</title>
</svelte:head>
 
<article>
  <h1>{data.post.title}</h1>
  <time datetime={data.post.date}>
    {new Date(data.post.date).toLocaleDateString('ja-JP')}
  </time>
  <div class="content">
    {@html data.post.content}
  </div>
</article>

フォームとアクション

SvelteKitは、プログレッシブエンハンスメントに対応したフォーム処理機能を提供します。JavaScriptが無効な環境でもフォームが機能し、JavaScriptが有効な場合はよりリッチな体験を提供できます。

フォーム送信は+page.server.jsactions関数で処理します。

JavaScript
// src/routes/contact/+page.server.js
import { fail } from '@sveltejs/kit';
 
/** @type {import('./$types').Actions} */
export const actions = {
  default: async ({ request }) => {
    const formData = await request.formData();
    const name = formData.get('name');
    const email = formData.get('email');
    const message = formData.get('message');
    
    // バリデーション
    if (!name || !email || !message) {
      return fail(400, {
        error: 'All fields are required',
        values: { name, email, message }
      });
    }
    
    // メール送信やDB保存などの処理
    await sendContactEmail({ name, email, message });
    
    return {
      success: true
    };
  }
};
Svelte
<!-- src/routes/contact/+page.svelte -->
<script>
  import { enhance } from '$app/forms';
  
  let { form } = $props();
</script>
 
<h1>Contact Us</h1>
 
{#if form?.success}
  <p class="success">Message sent successfully!</p>
{/if}
 
{#if form?.error}
  <p class="error">{form.error}</p>
{/if}
 
<form method="POST" use:enhance>
  <label>
    Name
    <input type="text" name="name" value={form?.values?.name ?? ''} required />
  </label>
  
  <label>
    Email
    <input type="email" name="email" value={form?.values?.email ?? ''} required />
  </label>
  
  <label>
    Message
    <textarea name="message" required>{form?.values?.message ?? ''}</textarea>
  </label>
  
  <button type="submit">Send</button>
</form>

use:enhanceディレクティブにより、JavaScriptが有効な環境ではページリロードなしでフォーム送信が行われます。

APIルート

+server.jsファイルでAPIエンドポイントを作成できます。

JavaScript
// src/routes/api/posts/+server.js
import { json } from '@sveltejs/kit';
import * as db from '$lib/server/database';
 
/** @type {import('./$types').RequestHandler} */
export async function GET({ url }) {
  const limit = Number(url.searchParams.get('limit') ?? 10);
  const posts = await db.getPosts(limit);
  
  return json(posts);
}
 
export async function POST({ request }) {
  const data = await request.json();
  const post = await db.createPost(data);
  
  return json(post, { status: 201 });
}

SSR/SSGの設定

SvelteKitは、**SSR(サーバーサイドレンダリング)SSG(静的サイト生成)**の両方をサポートしています。ページごとに異なるレンダリングモードを選択でき、アプリケーションの要件に合わせて最適な構成が可能です。


ページごとの設定

各ページでexport constを使用してレンダリングモードを設定できます。これにより、同じアプリ内でもページごとに異なる戦略を採用できます。

JavaScript
// src/routes/blog/[slug]/+page.js
 
// SSRを有効にする(デフォルト)
export const ssr = true;
 
// クライアントサイドレンダリングを有効にする
export const csr = true;
 
// 静的に事前レンダリングする
export const prerender = true;
 
// 動的パラメータの値を指定(プリレンダリング用)
export const entries = () => {
  return [
    { slug: 'first-post' },
    { slug: 'second-post' }
  ];
};

アダプターによるデプロイ

SvelteKitは様々なプラットフォームへのデプロイに対応しています。

JavaScript
// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
// または特定のアダプター
// import adapter from '@sveltejs/adapter-node';
// import adapter from '@sveltejs/adapter-static';
// import adapter from '@sveltejs/adapter-vercel';
// import adapter from '@sveltejs/adapter-cloudflare';
 
/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter()
  }
};
 
export default config;
アダプター用途
adapter-autoプラットフォーム自動検出
adapter-nodeNode.jsサーバー
adapter-static静的サイト生成
adapter-vercelVercel
adapter-cloudflareCloudflare Pages
adapter-netlifyNetlify

環境変数

SvelteKitでは、環境変数へのアクセス方法が明確に分離されています。これはセキュリティ上非常に重要で、プライベートな環境変数がクライアントに漏洩することを防ぎます。

JavaScript
// サーバーサイドのみ(+page.server.js, +server.js)
import { SECRET_API_KEY } from '$env/static/private';
import { DATABASE_URL } from '$env/dynamic/private';
 
// クライアントサイドでも使用可能
import { PUBLIC_API_URL } from '$env/static/public';
import { PUBLIC_FEATURE_FLAG } from '$env/dynamic/public';

.envファイルで定義:

Shell
# プライベート(サーバーのみ)
SECRET_API_KEY=your-secret-key
DATABASE_URL=postgres://...
 
# パブリック(クライアントでも使用可能)
PUBLIC_API_URL=https://api.example.com
PUBLIC_FEATURE_FLAG=true

実践的な例:ブログアプリ

プロジェクト構造

構造
src/
├── lib/
│   ├── components/
│   │   ├── PostCard.svelte
│   │   └── Navigation.svelte
│   └── server/
│       └── posts.js
├── routes/
│   ├── +layout.svelte
│   ├── +page.svelte
│   ├── +page.server.js
│   └── blog/
│       └── [slug]/
│           ├── +page.svelte
│           └── +page.server.js
└── app.css

レイアウト

Svelte
<!-- src/routes/+layout.svelte -->
<script>
  import '../app.css';
  import Navigation from '$lib/components/Navigation.svelte';
  
  let { children } = $props();
</script>
 
<div class="app">
  <Navigation />
  <main>
    {@render children()}
  </main>
  <footer>
    <p>&copy; 2026 My Blog</p>
  </footer>
</div>
 
<style>
  .app {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
  
  main {
    flex: 1;
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
    width: 100%;
  }
  
  footer {
    text-align: center;
    padding: 1rem;
    background: #f5f5f5;
  }
</style>

トップページ

JavaScript
// src/routes/+page.server.js
import { getPosts } from '$lib/server/posts';
 
/** @type {import('./$types').PageServerLoad} */
export async function load() {
  const posts = await getPosts();
  return { posts };
}
Svelte
<!-- src/routes/+page.svelte -->
<script>
  import PostCard from '$lib/components/PostCard.svelte';
  
  let { data } = $props();
</script>
 
<svelte:head>
  <title>My Blog</title>
</svelte:head>
 
<h1>Latest Posts</h1>
 
<div class="posts">
  {#each data.posts as post (post.slug)}
    <PostCard {post} />
  {/each}
</div>
 
<style>
  .posts {
    display: grid;
    gap: 1.5rem;
  }
  
  h1 {
    margin-bottom: 2rem;
    color: #ff3e00;
  }
</style>

PostCardコンポーネント

Svelte
<!-- src/lib/components/PostCard.svelte -->
<script>
  let { post } = $props();
</script>
 
<article>
  <a href="/blog/{post.slug}">
    <h2>{post.title}</h2>
    <time datetime={post.date}>
      {new Date(post.date).toLocaleDateString('ja-JP')}
    </time>
    <p>{post.excerpt}</p>
  </a>
</article>
 
<style>
  article {
    padding: 1.5rem;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    transition: box-shadow 0.2s, transform 0.2s;
  }
  
  article:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    transform: translateY(-2px);
  }
  
  a {
    text-decoration: none;
    color: inherit;
  }
  
  h2 {
    color: #ff3e00;
    margin-bottom: 0.5rem;
  }
  
  time {
    font-size: 0.875rem;
    color: #666;
  }
  
  p {
    margin-top: 1rem;
    color: #333;
    line-height: 1.6;
  }
</style>

パフォーマンス比較

Svelteは他のフレームワークと比較して優れたパフォーマンスを発揮します:

指標SvelteReactVue
バンドルサイズ(Hello World)~2KB~45KB~23KB
初期ロード時間高速中程度中程度
メモリ使用量少ない多い中程度
ランタイムオーバーヘッドなしありあり

まとめ

Svelteを選ぶべき場面

  • パフォーマンスが重要なプロジェクト
  • バンドルサイズを最小化したい場合
  • シンプルで直感的な開発体験を求める場合
  • HTML/CSS/JSの知識を最大限活かしたい場合

SvelteKitを選ぶべき場面

  • フルスタックWebアプリケーションを構築したい場合
  • SSR/SSGが必要な場合
  • ファイルベースルーティングを使いたい場合
  • フォーム処理とAPIルートを統合したい場合

Svelteは開発者満足度調査で常に上位にランクインしており、Stack Overflow 2024やState of JavaScript 2024でも高い評価を得ています。コンパイラベースのアプローチは、従来のフレームワークの限界を超える新しい可能性を示しています。

ぜひSvelteとSvelteKitで次のプロジェクトを始めてみてください!

参考リンク