T-CREATOR

SvelteKit 入門:サーバーサイドも静的もこれ一つで OK

SvelteKit 入門:サーバーサイドも静的もこれ一つで OK

SvelteKit の魅力と基本概念を理解し、実際にプロジェクトを作成して動作確認まで行う実践的な入門記事です。

Web 開発の世界は常に進化し続けています。React、Vue、Angular などのフレームワークが主流となった今、新たな選択肢として注目を集めているのが SvelteKit です。

「サーバーサイドレンダリングも静的サイト生成も、これ一つで完結する」という SvelteKit の魅力は、開発者の心を掴んで離しません。複雑な設定や複数のツールを使い分ける必要がなく、シンプルな開発体験を提供してくれるのです。

この記事では、SvelteKit の基本から実践的な使い方まで、初心者の方でも安心して学べるように丁寧に解説していきます。実際のコード例とエラー解決方法も含めて、あなたの SvelteKit 学習をサポートいたします。

SvelteKit とは何か

SvelteKit は、Svelte フレームワークの公式アプリケーションフレームワークです。従来の Svelte はクライアントサイドでの動作に特化していましたが、SvelteKit はサーバーサイドレンダリング(SSR)や静的サイト生成(SSG)まで幅広くサポートします。

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

React の Next.js や Vue の Nuxt.js と比較すると、SvelteKit の最大の特徴は「ゼロランタイム」にあります。Svelte はコンパイル時にフレームワークのコードを除去し、純粋な JavaScript を生成するため、バンドルサイズが小さく、パフォーマンスが優れています。

javascript// Svelte コンポーネントの例
<script>
  let count = 0;

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

<button on:click={increment}>
  クリック数: {count}
</button>

このコードはコンパイル時に以下のような最適化された JavaScript に変換されます:

javascript// コンパイル後のコード(簡略化)
function create_fragment(ctx) {
  let button;

  return {
    c() {
      button = element('button');
      button.textContent = `クリック数: ${count}`;
      button.addEventListener('click', increment);
    },
    // ... その他の最適化されたコード
  };
}

SvelteKit の設計思想

SvelteKit は「プログレッシブエンハンスメント」の考え方を採用しています。JavaScript が無効な環境でも基本的な機能が動作し、JavaScript が有効な環境では豊富なインタラクションを提供します。

SvelteKit の特徴と利点

SvelteKit の最大の魅力は、一つのフレームワークで多様なレンダリング方式をサポートしていることです。

レンダリング方式の柔軟性

SvelteKit では、ページごとに最適なレンダリング方式を選択できます:

レンダリング方式用途特徴
SSR(サーバーサイドレンダリング)動的コンテンツSEO 対応、初期表示が高速
SSG(静的サイト生成)ブログ、ドキュメントビルド時に生成、配信が高速
SPA(シングルページアプリ)ダッシュボードインタラクティブ、スムーズな遷移

パフォーマンスの優位性

SvelteKit のパフォーマンスは他のフレームワークと比較して際立っています:

javascript// バンドルサイズの比較例
// Next.js (React): ~130KB
// Nuxt.js (Vue): ~120KB
// SvelteKit: ~45KB

この差は、特にモバイル環境や低速回線での体感速度に大きく影響します。

開発体験の向上

SvelteKit の開発体験は直感的で、学習コストが低いことが特徴です:

javascript// ファイルベースルーティング
// src/routes/about/+page.svelte → /about
// src/routes/blog/[slug]/+page.svelte → /blog/hello-world

// サーバーサイドデータ取得
// src/routes/blog/[slug]/+page.server.js
export async function load({ params }) {
  const post = await getPost(params.slug);
  return { post };
}

開発環境のセットアップ

SvelteKit プロジェクトの作成から開発サーバーの起動まで、実際に手を動かしながら進めていきましょう。

前提条件の確認

まず、Node.js と Yarn がインストールされていることを確認します:

bash# Node.js のバージョン確認
node --version
# v18.0.0 以上を推奨

# Yarn のバージョン確認
yarn --version
# v1.22.0 以上を推奨

プロジェクトの作成

SvelteKit プロジェクトを作成します:

bash# SvelteKit プロジェクトの作成
yarn create svelte@latest my-sveltekit-app

# プロジェクトディレクトリに移動
cd my-sveltekit-app

# 依存関係のインストール
yarn install

プロジェクト作成時に以下の質問が表示されます:

bashWhich Svelte app template? › Skeleton project
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? › No

開発サーバーの起動

プロジェクトの作成が完了したら、開発サーバーを起動します:

bash# 開発サーバーの起動
yarn dev

正常に起動すると、以下のようなメッセージが表示されます:

bash  VITE v4.4.0  ready in 300 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

よくあるエラーと解決方法

エラー 1: Node.js のバージョンが古い

bashError: Node.js version 16.0.0 or higher is required

解決方法:

bash# Node.js のバージョンを確認
node --version

# 古い場合は Node.js をアップデート
# nvm を使用している場合
nvm install 18
nvm use 18

エラー 2: ポートが既に使用されている

bashError: listen EADDRINUSE: address already in use :::5173

解決方法:

bash# 別のポートで起動
yarn dev --port 3000

# または、使用中のプロセスを終了
lsof -ti:5173 | xargs kill -9

エラー 3: 依存関係のインストールエラー

basherror An unexpected error occurred: "ENOENT: no such file or directory"

解決方法:

bash# node_modules と yarn.lock を削除して再インストール
rm -rf node_modules yarn.lock
yarn install

プロジェクトの動作確認

ブラウザで http:​/​​/​localhost:5173 にアクセスすると、SvelteKit のウェルカムページが表示されます。

この段階で、あなたの SvelteKit 開発環境が整いました。次のステップでは、プロジェクトの構造を詳しく見ていきましょう。

プロジェクト構造の理解

SvelteKit プロジェクトの構造を理解することで、効率的な開発が可能になります。各ディレクトリとファイルの役割を詳しく見ていきましょう。

主要ディレクトリの役割

プロジェクトのルートディレクトリには、以下のような構造があります:

bashmy-sveltekit-app/
├── src/
│   ├── routes/          # ページとルーティング
│   ├── lib/             # 共有ライブラリ
│   ├── app.html         # HTML テンプレート
│   └── app.css          # グローバルスタイル
├── static/              # 静的ファイル
├── svelte.config.js     # SvelteKit 設定
├── vite.config.js       # Vite 設定
└── package.json         # 依存関係

src/routes ディレクトリの重要性

SvelteKit の最大の特徴であるファイルベースルーティングは、src​/​routes ディレクトリで管理されます:

bashsrc/routes/
├── +page.svelte         # ホームページ (/)
├── about/
│   └── +page.svelte     # アバウトページ (/about)
├── blog/
│   ├── +page.svelte     # ブログ一覧 (/blog)
│   └── [slug]/
│       └── +page.svelte # 個別記事 (/blog/hello-world)
└── api/
    └── users/
        └── +server.js   # API エンドポイント (/api/users)

特殊ファイルの役割

SvelteKit では、特定の名前のファイルが特別な意味を持ちます:

javascript// +page.svelte - ページコンポーネント
// +page.server.js - サーバーサイドロード関数
// +page.js - クライアントサイドロード関数
// +layout.svelte - レイアウトコンポーネント
// +layout.server.js - レイアウトレベルでのサーバーサイド処理
// +server.js - API ルート
// +error.svelte - エラーページ

設定ファイルの理解

svelte.config.js は SvelteKit の動作を制御する重要なファイルです:

javascript// svelte.config.js
import adapter from '@sveltejs/adapter-auto';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter(),
    // プリレンダリングの設定
    prerender: {
      handleHttpError: ({ path, referrer, message }) => {
        // 404 エラーの処理
        if (path.startsWith('/blog/')) {
          return;
        }
        throw new Error(message);
      },
    },
  },
};

export default config;

基本的なページ作成

実際にページを作成しながら、SvelteKit の基本的な機能を学んでいきましょう。

シンプルなページの作成

まず、src​/​routes​/​about​/​+page.svelte を作成します:

svelte<!-- src/routes/about/+page.svelte -->
<script>
  let title = "アバウトページ";
  let description = "SvelteKit で作成されたページです";
</script>

<svelte:head>
  <title>{title}</title>
  <meta name="description" content={description} />
</svelte:head>

<main>
  <h1>{title}</h1>
  <p>{description}</p>

  <div class="features">
    <h2>主な機能</h2>
    <ul>
      <li>サーバーサイドレンダリング</li>
      <li>静的サイト生成</li>
      <li>ファイルベースルーティング</li>
      <li>ゼロランタイム</li>
    </ul>
  </div>
</main>

<style>
  main {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  h1 {
    color: #333;
    border-bottom: 2px solid #007acc;
    padding-bottom: 0.5rem;
  }

  .features {
    margin-top: 2rem;
    padding: 1rem;
    background: #f5f5f5;
    border-radius: 8px;
  }

  ul {
    list-style-type: none;
    padding: 0;
  }

  li {
    padding: 0.5rem 0;
    border-bottom: 1px solid #ddd;
  }

  li:last-child {
    border-bottom: none;
  }
</style>

動的ルーティングの実装

ブログ記事のような動的コンテンツを作成してみましょう:

svelte<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  export let data;

  $: ({ post } = data);
</script>

<svelte:head>
  <title>{post.title}</title>
  <meta name="description" content={post.excerpt} />
</svelte:head>

<article>
  <header>
    <h1>{post.title}</h1>
    <time datetime={post.date}>{post.date}</time>
  </header>

  <div class="content">
    {@html post.content}
  </div>

  <footer>
    <a href="/blog">← ブログ一覧に戻る</a>
  </footer>
</article>

<style>
  article {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  header {
    margin-bottom: 2rem;
    padding-bottom: 1rem;
    border-bottom: 2px solid #007acc;
  }

  h1 {
    color: #333;
    margin-bottom: 0.5rem;
  }

  time {
    color: #666;
    font-size: 0.9rem;
  }

  .content {
    line-height: 1.6;
    margin-bottom: 2rem;
  }

  footer {
    margin-top: 2rem;
    padding-top: 1rem;
    border-top: 1px solid #ddd;
  }

  a {
    color: #007acc;
    text-decoration: none;
  }

  a:hover {
    text-decoration: underline;
  }
</style>

サーバーサイドデータ取得の実装

動的ルーティングで使用するデータを取得するためのサーバーサイドロード関数を作成します:

javascript// src/routes/blog/[slug]/+page.server.js
export async function load({ params }) {
  try {
    // 実際のプロジェクトでは、データベースや CMS から取得
    const posts = {
      'hello-world': {
        title: 'SvelteKit で Hello World',
        date: '2024-01-15',
        excerpt: 'SvelteKit の基本を学びましょう',
        content:
          '<p>SvelteKit は素晴らしいフレームワークです...</p>',
      },
      'getting-started': {
        title: 'SvelteKit 入門ガイド',
        date: '2024-01-10',
        excerpt: 'SvelteKit の始め方を解説します',
        content:
          '<p>この記事では SvelteKit の基本を...</p>',
      },
    };

    const post = posts[params.slug];

    if (!post) {
      throw new Error('記事が見つかりません');
    }

    return { post };
  } catch (error) {
    // エラーハンドリング
    console.error('記事の取得に失敗しました:', error);
    throw error;
  }
}

よくあるエラーと解決方法

エラー 1: 動的ルートでデータが取得できない

bashError: Cannot read properties of undefined (reading 'title')

解決方法:

javascript// +page.svelte でデータの存在確認を追加
<script>
  export let data;

  $: ({ post } = data || {});

  // データが存在しない場合の処理
  if (!post) {
    throw new Error('記事が見つかりません');
  }
</script>

エラー 2: ルーティングが正しく動作しない

bashError: Page not found

解決方法:

bash# ファイル名とディレクトリ構造を確認
# +page.svelte の名前が正確かチェック
# 大文字小文字も区別されるので注意

サーバーサイドレンダリングの活用

SvelteKit の真価が発揮されるサーバーサイドレンダリングについて、実践的な例で学んでいきましょう。

SSR の仕組み理解

サーバーサイドレンダリングでは、ページの初期 HTML がサーバーで生成され、クライアントに送信されます。これにより、SEO の向上と初期表示の高速化が実現されます。

javascript// src/routes/products/+page.server.js
export async function load({ fetch }) {
  try {
    // 外部 API からデータを取得
    const response = await fetch(
      'https://api.example.com/products'
    );

    if (!response.ok) {
      throw new Error(
        `HTTP error! status: ${response.status}`
      );
    }

    const products = await response.json();

    return {
      products,
      timestamp: new Date().toISOString(),
    };
  } catch (error) {
    console.error('商品データの取得に失敗:', error);
    return {
      products: [],
      error: '商品データの取得に失敗しました',
    };
  }
}

SSR 対応ページの作成

商品一覧ページを作成して、SSR の効果を確認しましょう:

svelte<!-- src/routes/products/+page.svelte -->
<script>
  export let data;

  $: ({ products, timestamp, error } = data);
</script>

<svelte:head>
  <title>商品一覧 - SvelteKit デモ</title>
  <meta name="description" content="SvelteKit で作成された商品一覧ページです" />
</svelte:head>

<main>
  <h1>商品一覧</h1>

  {#if error}
    <div class="error">
      <p>{error}</p>
    </div>
  {:else if products.length === 0}
    <p>商品が見つかりませんでした。</p>
  {:else}
    <div class="products-grid">
      {#each products as product}
        <div class="product-card">
          <h3>{product.name}</h3>
          <p class="price">¥{product.price.toLocaleString()}</p>
          <p class="description">{product.description}</p>
        </div>
      {/each}
    </div>
  {/if}

  <div class="server-info">
    <p>サーバーサイドレンダリング時刻: {timestamp}</p>
  </div>
</main>

<style>
  main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem;
  }

  h1 {
    color: #333;
    margin-bottom: 2rem;
  }

  .error {
    background: #fee;
    color: #c33;
    padding: 1rem;
    border-radius: 4px;
    margin-bottom: 1rem;
  }

  .products-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 2rem;
    margin-bottom: 2rem;
  }

  .product-card {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1.5rem;
    background: white;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  }

  .product-card h3 {
    margin: 0 0 0.5rem 0;
    color: #333;
  }

  .price {
    font-size: 1.2rem;
    font-weight: bold;
    color: #007acc;
    margin: 0.5rem 0;
  }

  .description {
    color: #666;
    line-height: 1.5;
  }

  .server-info {
    margin-top: 2rem;
    padding: 1rem;
    background: #f5f5f5;
    border-radius: 4px;
    font-size: 0.9rem;
    color: #666;
  }
</style>

SSR の動作確認

SSR が正しく動作しているかを確認する方法:

bash# ページのソースを確認(JavaScript を無効にしてアクセス)
# または、curl で HTML を取得
curl http://localhost:5173/products

正常に SSR が動作している場合、HTML に商品データが含まれているはずです。

静的サイト生成の実装

SvelteKit のもう一つの強力な機能である静的サイト生成について学びましょう。

SSG の設定

静的サイト生成を有効にするには、svelte.config.js でアダプターを設定します:

javascript// svelte.config.js
import adapter from '@sveltejs/adapter-static';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      // 静的ファイルの出力先
      pages: 'build',
      assets: 'build',
      fallback: '404.html',
      precompress: false,
      strict: true,
    }),
    // プリレンダリングの設定
    prerender: {
      entries: ['*'],
    },
  },
};

export default config;

静的ページの作成

ブログ記事のような静的コンテンツを作成します:

svelte<!-- src/routes/blog/+page.svelte -->
<script>
  export let data;

  $: ({ posts } = data);
</script>

<svelte:head>
  <title>ブログ - SvelteKit デモ</title>
  <meta name="description" content="SvelteKit で作成されたブログです" />
</svelte:head>

<main>
  <h1>ブログ記事一覧</h1>

  <div class="posts-grid">
    {#each posts as post}
      <article class="post-card">
        <h2>
          <a href="/blog/{post.slug}">{post.title}</a>
        </h2>
        <time datetime={post.date}>{post.date}</time>
        <p>{post.excerpt}</p>
        <div class="tags">
          {#each post.tags as tag}
            <span class="tag">{tag}</span>
          {/each}
        </div>
      </article>
    {/each}
  </div>
</main>

<style>
  main {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  h1 {
    color: #333;
    margin-bottom: 2rem;
  }

  .posts-grid {
    display: grid;
    gap: 2rem;
  }

  .post-card {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1.5rem;
    background: white;
  }

  .post-card h2 {
    margin: 0 0 0.5rem 0;
  }

  .post-card h2 a {
    color: #333;
    text-decoration: none;
  }

  .post-card h2 a:hover {
    color: #007acc;
  }

  time {
    color: #666;
    font-size: 0.9rem;
  }

  .post-card p {
    margin: 1rem 0;
    line-height: 1.6;
    color: #555;
  }

  .tags {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
  }

  .tag {
    background: #007acc;
    color: white;
    padding: 0.2rem 0.5rem;
    border-radius: 12px;
    font-size: 0.8rem;
  }
</style>

静的データの準備

ブログ記事のデータを準備します:

javascript// src/routes/blog/+page.server.js
export async function load() {
  // 実際のプロジェクトでは、Markdown ファイルや CMS から取得
  const posts = [
    {
      slug: 'hello-world',
      title: 'SvelteKit で Hello World',
      date: '2024-01-15',
      excerpt:
        'SvelteKit の基本を学びましょう。ゼロランタイムの魅力を体験できます。',
      tags: ['SvelteKit', '入門'],
    },
    {
      slug: 'getting-started',
      title: 'SvelteKit 入門ガイド',
      date: '2024-01-10',
      excerpt:
        'SvelteKit の始め方を詳しく解説します。開発環境の構築から最初のページ作成まで。',
      tags: ['SvelteKit', 'チュートリアル'],
    },
    {
      slug: 'ssr-vs-ssg',
      title: 'SSR と SSG の使い分け',
      date: '2024-01-05',
      excerpt:
        'サーバーサイドレンダリングと静的サイト生成の違いと、適切な選択方法について。',
      tags: ['SvelteKit', 'SSR', 'SSG'],
    },
  ];

  return { posts };
}

静的サイトのビルド

静的サイトをビルドして確認します:

bash# 静的サイトのビルド
yarn build

# ビルド結果の確認
ls build/

ビルドが成功すると、build ディレクトリに静的ファイルが生成されます。

よくあるエラーと解決方法

エラー 1: プリレンダリングエラー

bashError: Cannot prerender pages with server-side data loading

解決方法:

javascript// +page.server.js でプリレンダリング可能なデータのみを返す
export const prerender = true;

export async function load() {
  // 静的データのみを返す
  return { posts: staticPosts };
}

エラー 2: 動的ルートのプリレンダリング

bashError: Cannot prerender pages with dynamic routes

解決方法:

javascript// svelte.config.js でエントリーポイントを指定
prerender: {
  entries: [
    '/',
    '/about',
    '/blog/hello-world',
    '/blog/getting-started',
  ];
}

データフェッチングの実装

SvelteKit では、サーバーサイドとクライアントサイドで異なるデータフェッチング方法があります。それぞれの特徴と使い分けを学びましょう。

サーバーサイドデータフェッチング

サーバーサイドでデータを取得する場合、+page.server.js を使用します:

javascript// src/routes/api-demo/+page.server.js
export async function load({ fetch, url }) {
  try {
    // クエリパラメータの取得
    const searchTerm = url.searchParams.get('q') || '';

    // 外部 API からのデータ取得
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?title_like=${searchTerm}`
    );

    if (!response.ok) {
      throw new Error(
        `HTTP error! status: ${response.status}`
      );
    }

    const posts = await response.json();

    return {
      posts,
      searchTerm,
      timestamp: new Date().toISOString(),
    };
  } catch (error) {
    console.error('データ取得エラー:', error);
    return {
      posts: [],
      searchTerm: '',
      error: 'データの取得に失敗しました',
      timestamp: new Date().toISOString(),
    };
  }
}

クライアントサイドデータフェッチング

クライアントサイドでデータを取得する場合、+page.js を使用します:

javascript// src/routes/client-demo/+page.js
export async function load({ fetch }) {
  // クライアントサイドでのみ実行される
  return {
    message: 'このデータはクライアントサイドで取得されます',
  };
}

リアルタイムデータ更新の実装

クライアントサイドでリアルタイムにデータを更新する例:

svelte<!-- src/routes/realtime/+page.svelte -->
<script>
  import { onMount } from 'svelte';

  let posts = [];
  let loading = false;
  let error = null;

  async function fetchPosts() {
    loading = true;
    error = null;

    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      posts = await response.json();
    } catch (err) {
      error = 'データの取得に失敗しました';
      console.error('Fetch error:', err);
    } finally {
      loading = false;
    }
  }

  onMount(() => {
    fetchPosts();

    // 5秒ごとにデータを更新
    const interval = setInterval(fetchPosts, 5000);

    return () => clearInterval(interval);
  });
</script>

<svelte:head>
  <title>リアルタイムデータ - SvelteKit デモ</title>
</svelte:head>

<main>
  <h1>リアルタイムデータ更新</h1>

  <button on:click={fetchPosts} disabled={loading}>
    {loading ? '更新中...' : '手動更新'}
  </button>

  {#if error}
    <div class="error">
      <p>{error}</p>
    </div>
  {/if}

  {#if loading && posts.length === 0}
    <p>データを読み込み中...</p>
  {:else}
    <div class="posts">
      {#each posts as post}
        <div class="post">
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </div>
      {/each}
    </div>
  {/if}
</main>

<style>
  main {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  h1 {
    color: #333;
    margin-bottom: 1rem;
  }

  button {
    background: #007acc;
    color: white;
    border: none;
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    margin-bottom: 1rem;
  }

  button:disabled {
    background: #ccc;
    cursor: not-allowed;
  }

  .error {
    background: #fee;
    color: #c33;
    padding: 1rem;
    border-radius: 4px;
    margin-bottom: 1rem;
  }

  .posts {
    display: grid;
    gap: 1rem;
  }

  .post {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1rem;
    background: white;
  }

  .post h3 {
    margin: 0 0 0.5rem 0;
    color: #333;
  }

  .post p {
    margin: 0;
    color: #666;
    line-height: 1.5;
  }
</style>

フォーム送信とデータ更新

フォームを使用したデータ送信の実装:

svelte<!-- src/routes/contact/+page.svelte -->
<script>
  import { enhance } from '$app/forms';

  let submitting = false;
  let success = false;
  let error = null;

  function handleSubmit() {
    submitting = true;
    success = false;
    error = null;
  }
</script>

<svelte:head>
  <title>お問い合わせ - SvelteKit デモ</title>
</svelte:head>

<main>
  <h1>お問い合わせ</h1>

  {#if success}
    <div class="success">
      <p>お問い合わせを送信しました。ありがとうございます。</p>
    </div>
  {:else}
    <form method="POST" use:enhance={handleSubmit}>
      <div class="form-group">
        <label for="name">お名前</label>
        <input type="text" id="name" name="name" required />
      </div>

      <div class="form-group">
        <label for="email">メールアドレス</label>
        <input type="email" id="email" name="email" required />
      </div>

      <div class="form-group">
        <label for="message">メッセージ</label>
        <textarea id="message" name="message" rows="5" required></textarea>
      </div>

      <button type="submit" disabled={submitting}>
        {submitting ? '送信中...' : '送信'}
      </button>
    </form>
  {/if}

  {#if error}
    <div class="error">
      <p>{error}</p>
    </div>
  {/if}
</main>

<style>
  main {
    max-width: 600px;
    margin: 0 auto;
    padding: 2rem;
  }

  h1 {
    color: #333;
    margin-bottom: 2rem;
  }

  .form-group {
    margin-bottom: 1rem;
  }

  label {
    display: block;
    margin-bottom: 0.5rem;
    color: #333;
    font-weight: bold;
  }

  input, textarea {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 1rem;
  }

  button {
    background: #007acc;
    color: white;
    border: none;
    padding: 0.75rem 1.5rem;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
  }

  button:disabled {
    background: #ccc;
    cursor: not-allowed;
  }

  .success {
    background: #d4edda;
    color: #155724;
    padding: 1rem;
    border-radius: 4px;
    margin-bottom: 1rem;
  }

  .error {
    background: #fee;
    color: #c33;
    padding: 1rem;
    border-radius: 4px;
    margin-bottom: 1rem;
  }
</style>

フォーム処理のサーバーサイド実装:

javascript// src/routes/contact/+page.server.js
export async function actions({ request }) {
  try {
    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 {
        success: false,
        error: 'すべての項目を入力してください',
      };
    }

    // 実際のプロジェクトでは、データベースに保存またはメール送信
    console.log('お問い合わせ受信:', {
      name,
      email,
      message,
    });

    return {
      success: true,
    };
  } catch (error) {
    console.error('フォーム処理エラー:', error);
    return {
      success: false,
      error:
        '送信に失敗しました。しばらく時間をおいて再度お試しください。',
    };
  }
}

本番環境へのデプロイ

SvelteKit アプリケーションを本番環境にデプロイする方法を学びましょう。主要なプラットフォームでのデプロイ手順を解説します。

Vercel へのデプロイ

Vercel は SvelteKit の公式サポートがあり、最も簡単にデプロイできます:

bash# Vercel CLI のインストール
yarn global add vercel

# プロジェクトのルートで実行
vercel

デプロイ時の設定:

javascript// vercel.json(必要に応じて)
{
  "buildCommand": "yarn build",
  "outputDirectory": "build",
  "framework": "sveltekit"
}

Netlify へのデプロイ

Netlify でも簡単にデプロイできます:

bash# Netlify CLI のインストール
yarn global add netlify-cli

# ビルドとデプロイ
yarn build
netlify deploy --prod --dir=build

Netlify の設定ファイル:

toml# netlify.toml
[build]
  command = "yarn build"
  publish = "build"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

GitHub Pages へのデプロイ

静的サイトとして GitHub Pages にデプロイする場合:

javascript// svelte.config.js
import adapter from '@sveltejs/adapter-static';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      pages: 'build',
      assets: 'build',
      fallback: '404.html',
      precompress: false,
      strict: true,
    }),
    prerender: {
      entries: ['*'],
    },
    paths: {
      base:
        process.env.NODE_ENV === 'production'
          ? '/your-repo-name'
          : '',
    },
  },
};

export default config;

GitHub Actions での自動デプロイ:

yaml# .github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Build
        run: yarn build
        env:
          NODE_ENV: production

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./build

環境変数の設定

本番環境での環境変数設定:

bash# .env.production
PUBLIC_API_URL=https://api.example.com
DATABASE_URL=your-production-database-url
javascript// src/lib/config.js
export const config = {
  apiUrl:
    import.meta.env.PUBLIC_API_URL ||
    'http://localhost:3000',
  isProduction: import.meta.env.PROD,
};

パフォーマンス最適化

本番環境でのパフォーマンス最適化:

javascript// svelte.config.js
import adapter from '@sveltejs/adapter-auto';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter(),
    // プリロードの設定
    prerender: {
      handleHttpError: ({ path, referrer, message }) => {
        if (path.startsWith('/blog/')) {
          return;
        }
        throw new Error(message);
      },
    },
  },
};

export default config;

よくあるデプロイエラーと解決方法

エラー 1: ビルドエラー

bashError: Cannot find module '@sveltejs/adapter-auto'

解決方法:

bash# アダプターのインストール
yarn add -D @sveltejs/adapter-auto

# または静的サイト用
yarn add -D @sveltejs/adapter-static

エラー 2: ルーティングエラー

bashError: 404 Not Found

解決方法:

javascript// svelte.config.js で fallback を設定
adapter: adapter({
  fallback: '404.html',
});

エラー 3: 環境変数エラー

bashError: Environment variable not found

解決方法:

bash# デプロイプラットフォームで環境変数を設定
# Vercel: ダッシュボード → Settings → Environment Variables
# Netlify: ダッシュボード → Site settings → Environment variables

まとめ

SvelteKit の魅力と実践的な使い方を学んでいただきました。この記事を通じて、以下の重要なポイントを理解していただけたと思います:

SvelteKit の核心的な価値

SvelteKit の最大の魅力は、一つのフレームワークで多様な Web アプリケーションのニーズに対応できることです。サーバーサイドレンダリング、静的サイト生成、シングルページアプリケーションなど、プロジェクトの要件に応じて最適なアプローチを選択できます。

開発体験の向上

従来の複雑な設定や複数のツールを使い分ける必要がなく、シンプルで直感的な開発体験を提供してくれます。ファイルベースルーティングや自動的なコード分割により、開発者はビジネスロジックに集中できます。

パフォーマンスの優位性

ゼロランタイムの設計により、バンドルサイズが小さく、初期表示が高速です。特にモバイル環境や低速回線での体感速度は、他のフレームワークと比較して際立っています。

学習の次のステップ

SvelteKit の基本をマスターしたあなたは、次のような発展的な学習に進むことができます:

  • TypeScript との組み合わせ:型安全性を活かした開発
  • 状態管理:Svelte のストア機能を活用したグローバル状態管理
  • 認証システム:セッション管理とユーザー認証の実装
  • データベース連携:Prisma や Supabase との統合
  • テスト戦略:Vitest や Playwright を使ったテスト実装

実践的なアドバイス

SvelteKit を学ぶ上で最も重要なのは、実際に手を動かしてプロジェクトを作成することです。この記事で紹介したコード例を参考に、自分なりのアプリケーションを作成してみてください。

エラーに遭遇した場合は、SvelteKit の公式ドキュメントやコミュニティフォーラムを活用してください。SvelteKit のコミュニティは非常に活発で、親切なサポートを受けることができます。

SvelteKit は、Web 開発の未来を切り開く革新的なフレームワークです。あなたの開発スキルを次のレベルに引き上げる素晴らしいツールとなることでしょう。

関連リンク