Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

【2025年最新】Svelteフレームワーク入門:初心者でも理解できる特徴と基本構文

記事のサムネイル

Svelteは従来のJavaScriptフレームワークとは一線を画す、革新的なアプローチで注目を集めています。ビルド時コンパイルによる高速なパフォーマンスと、シンプルな記述で直感的に開発できる特徴が魅力です。本記事では、Svelteを初めて学ぶ方に向けて、その基本概念から実践的な使い方までを解説します。

Svelteとは?他のフレームワークとの違いを理解する

Svelteは2016年にRich Harris氏によって開発された比較的新しいJavaScriptフレームワークです。「コンパイラとしてのフレームワーク」という特徴を持ち、ReactやVueとは根本的に異なるアプローチを取っています。

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

Svelteの最大の特徴は、実行時(ランタイム)ではなくビルド時にコードを変換するコンパイラとして機能することです。

// Svelteのコンポーネント例
<script>
  let count = 0;
  
  function increment() {
    count += 1;
  }
</script>

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

このコードはビルド時に最適化された純粋なJavaScriptに変換されるため:

  1. 仮想DOM(Virtual DOM)が不要
  2. バンドルサイズが小さい
  3. 実行速度が速い

という大きなメリットを得られます。

ReactやVueとの比較

フレームワーク アプローチ バンドルサイズ パフォーマンス 学習曲線
React 仮想DOM 大きい 中〜高 中〜高
Vue 仮想DOM 中程度 中〜高
Svelte コンパイラ 小さい

Reactでは仮想DOMを使用してUI更新を管理し、状態変更を追跡するために複雑な仕組みが必要です。一方、Svelteは直接DOMを更新する最適化されたコードを生成するため、余分なオーバーヘッドがありません。

Svelteの主な利点

  1. より少ないコード:ボイラープレートが少なく、同じ機能を実装するのに必要なコード量が少なくて済みます。

  2. 直感的な構文:HTML、CSS、JavaScriptを自然に組み合わせられる構文で、新しい概念を覚える必要が少ないです。

  3. 真のリアクティビティ:変数への代入だけで自動的にUIが更新される、シンプルで直感的なリアクティビティシステムを提供します。

// Reactの場合
const [count, setCount] = useState(0);
function increment() {
  setCount(count + 1); // 特別な関数を呼び出す必要がある
}

// Svelteの場合
let count = 0;
function increment() {
  count += 1; // 普通の変数代入だけでUIが更新される
}
  1. 高速なパフォーマンス:ランタイムライブラリが最小限で、最適化されたJavaScriptを生成します。

Svelteはシンプルさとパフォーマンスの両立を実現した現代的なフレームワークで、特に小〜中規模のプロジェクトや、パフォーマンスが重要なアプリケーションに適しています。これからWeb開発を学ぶ初心者にとっても、学習の負担が少ない選択肢といえるでしょう。

Svelte開発環境の構築手順

Svelteを始めるには開発環境を整える必要がありますが、公式が提供するテンプレートを使えば簡単に始められます。ここでは最新の開発環境構築方法を紹介します。

必要なツール

  1. Node.js:JavaScriptのランタイム環境
  2. npmまたはpnpm:パッケージマネージャー
  3. エディタ:Visual Studio Code(Svelte拡張機能あり)がおすすめ

SvelteKitを使った開発環境構築(推奨)

SvelteKitは、Svelteの公式アプリケーションフレームワークで、ルーティングやサーバーサイドレンダリングなどを含む完全なフロントエンド開発環境を提供します。

# create-svelte コマンドを実行
npm create svelte@latest my-svelte-app

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

# 依存関係をインストール
npm install

# 開発サーバーを起動
npm run dev

コマンドを実行すると、いくつかの質問が表示されます:

  1. プロジェクトの種類(SkeltonプロジェクトかDemoアプリか)
  2. TypeScriptを使用するかどうか
  3. ESLintやPrettierなどの追加機能

初心者の方は「Demo app」を選ぶと、参考になるコードが含まれたアプリが生成されるのでおすすめです。

Viteを使ったシンプルなSvelteプロジェクト

より軽量な環境が欲しい場合は、ViteというビルドツールでSvelteプロジェクトを作成できます。

# Viteを使ってSvelteプロジェクトを作成
npm create vite@latest my-svelte-app -- --template svelte

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

# 依存関係をインストール
npm install

# 開発サーバーを起動
npm run dev

こちらはシンプルなクライアントサイドのSvelteアプリケーションを作成します。フルスタックなアプリを作りたい場合はSvelteKitを使うべきですが、軽量なアプリを素早く開発したい場合に適しています。

プロジェクト構造の理解

SvelteKitプロジェクトの基本構造は以下のようになっています:

my-svelte-app/
├── src/
│   ├── routes/              // ルーティング用のコンポーネント
│   │   ├── +page.svelte     // トップページ
│   │   └── ... 
│   ├── lib/                 // 再利用可能なコンポーネントやユーティリティ
│   └── app.html             // HTMLのテンプレート
├── static/                  // 画像やフォントなどの静的ファイル
├── svelte.config.js         // Svelte設定ファイル
└── package.json             // 依存関係

この構造は、コードを整理しやすく、大規模なアプリケーション開発にも対応できる設計になっています。

エディタの設定

Visual Studio Codeを使用している場合、Svelte開発を効率化する優れた拡張機能があります:

  1. Svelte for VS Code:構文ハイライト、コード補完、エラーチェックなどの機能を提供
  2. Prettier:コードフォーマッター(Svelteファイルもサポート)

これらをインストールすることで、Svelteの開発体験が大幅に向上します。

開発環境が整ったら、次はSvelteの基本構文とコンポーネントの作り方を学びましょう。

Svelteの基本構文とコンポーネントの作り方

Svelteコンポーネントは、HTML、CSS、JavaScriptを単一のファイルに組み合わせます。これにより、コードの管理が容易になり、開発効率が向上します。

Svelteファイルの基本構造

Svelteファイル(.svelte)の基本構造は次のとおりです:

<script>
  // JavaScriptコード
</script>

<style>
  /* CSSスタイル */
</style>

<!-- HTML/コンポーネントのマークアップ -->

それぞれのセクションについて詳しく見ていきましょう。

スクリプトセクション

<script>タグ内にJavaScriptのロジックを記述します:

<script>
  // 変数の宣言
  let name = 'Svelte初心者';
  let count = 0;
  
  // 関数の定義
  function incrementCount() {
    count += 1;  // 変数に代入するだけでUIが自動的に更新される
  }
  
  // props(親コンポーネントから渡されるデータ)の宣言
  export let title = 'デフォルトタイトル';  // デフォルト値も設定可能
</script>

ポイント:

  • export let 構文でpropsを定義します
  • 通常の変数宣言と関数定義が可能です
  • 変数を更新すると、それを参照しているUIが自動的に更新されます

スタイルセクション

<style>タグ内にCSSを記述します:

<style>
  /* このスタイルはコンポーネント内でスコープされる */
  h1 {
    color: #ff3e00;
    font-size: 2em;
  }
  
  .counter {
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 0.5em;
    margin: 1em 0;
  }
  
  button {
    background-color: #ff3e00;
    color: white;
    border: none;
    padding: 0.5em 1em;
    border-radius: 4px;
    cursor: pointer;
  }
</style>

ポイント:

  • スタイルはデフォルトでコンポーネント内にスコープされるため、他のコンポーネントに影響しません
  • 通常のCSSをそのまま使用できます
  • グローバルスタイルを適用したい場合は :global(セレクタ) を使用します

マークアップセクション

HTMLとSvelteの特殊なディレクティブを組み合わせたマークアップを記述します:

<h1>{title}</h1>

<p>こんにちは、{name}さん!</p>

<div class="counter">
  <p>カウント: {count}</p>
  <button on:click={incrementCount}>クリック</button>
</div>

{#if count > 10}
  <p>10回以上クリックしました!</p>
{:else}
  <p>もっとクリックしてください</p>
{/if}

Svelteの基本的なディレクティブ

  1. 変数の表示:中括弧 {変数} で変数を表示
<p>こんにちは、{name}さん!</p>
  1. 条件付きレンダリング{#if}...{:else}...{/if}
{#if count > 5}
  <p>カウントが5を超えました!</p>
{:else if count > 0}
  <p>カウントは1〜5の間です</p>
{:else}
  <p>まだカウントは0です</p>
{/if}
  1. 繰り返し(ループ){#each}...{/each}
<script>
  let fruits = ['りんご', 'バナナ', 'オレンジ'];
</script>

<ul>
  {#each fruits as fruit, index}
    <li>{index + 1}. {fruit}</li>
  {/each}
</ul>
  1. イベントハンドリングon:イベント名={ハンドラ関数}
<button on:click={handleClick}>クリック</button>
  1. プロパティバインディングbind:プロパティ={変数}
<input bind:value={name} placeholder="名前を入力">
<p>こんにちは、{name}さん!</p>

コンポーネントの作成と使用

コンポーネントはSvelteの基本的な構成要素です。コンポーネントを作成・使用する方法を見ていきましょう。

  1. 新しいコンポーネントの作成:例えば Button.svelte として以下のようなファイルを作成します
<!-- Button.svelte -->
<script>
  export let text = 'ボタン';
  export let primary = false;
  
  function handleClick() {
    // イベントをディスパッチ(発生)させる
    dispatch('click');
  }
  
  // カスタムイベントを定義
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();
</script>

<style>
  button {
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    border: none;
    background-color: #ccc;
  }
  
  .primary {
    background-color: #ff3e00;
    color: white;
  }
</style>

<button 
  class:primary={primary}
  on:click={handleClick}
>
  {text}
</button>
  1. コンポーネントの使用:別のコンポーネントでインポートして使用します
<!-- App.svelte -->
<script>
  import Button from './Button.svelte';
  
  function handleButtonClick() {
    alert('ボタンがクリックされました!');
  }
</script>

<div>
  <h1>Svelteコンポーネントの例</h1>
  
  <Button text="通常ボタン" on:click={handleButtonClick} />
  <Button text="メインボタン" primary={true} on:click={handleButtonClick} />
</div>

これらの基本を押さえれば、Svelteでのコンポーネント開発を始めることができます。次のセクションでは、より高度な状態管理とイベントハンドリングについて学んでいきましょう。

リアクティブな状態管理とイベントハンドリング

Svelteの最も魅力的な機能の一つが、シンプルで直感的なリアクティブ(反応的)な状態管理です。このセクションでは、Svelteのリアクティブな状態管理とイベント処理について詳しく解説します。

リアクティブな変数と宣言

Svelteでは、変数を更新するだけでUIに反映されるリアクティブな動作が特徴です。さらに、他の変数に依存した計算もシンプルに記述できます。

<script>
  let count = 0;
  
  // リアクティブな宣言(countが変わるたびに自動計算される)
  $: doubled = count * 2;
  $: squared = count * count;
  
  // 複数の文を含むリアクティブブロックも可能
  $: {
    console.log(`カウントが${count}に変わりました`);
    if (count >= 10) {
      alert('カウントが10以上になりました!');
      // 値をリセット
      count = 0;
    }
  }
  
  function increment() {
    count += 1;
  }
</script>

<button on:click={increment}>
  カウント: {count}
</button>

<p>2倍: {doubled}</p>
<p>2乗: {squared}</p>

$: で始まる構文は「リアクティブ宣言」と呼ばれ、依存する値が変更されるたびに再評価されます。

バインディング

Svelteには双方向バインディングが簡単に実装できる機能があります:

<script>
  let name = '';
  let agreement = false;
  let selectedColor = 'red';
  let selectedColors = ['red'];
</script>

<!-- 入力フィールドとバインド -->
<input bind:value={name} placeholder="名前を入力">
<p>こんにちは、{name || 'ゲスト'}さん!</p>

<!-- チェックボックスとバインド -->
<label>
  <input type="checkbox" bind:checked={agreement}>
  利用規約に同意する
</label>
{#if agreement}
  <p>ありがとうございます!</p>
{:else}
  <p>続行するには規約に同意してください。</p>
{/if}

<!-- セレクトボックスとバインド -->
<select bind:value={selectedColor}>
  <option value="red"></option>
  <option value="green"></option>
  <option value="blue"></option>
</select>
<p style="color: {selectedColor}">選択した色: {selectedColor}</p>

<!-- 複数選択とバインド -->
<select multiple bind:value={selectedColors}>
  <option value="red"></option>
  <option value="green"></option>
  <option value="blue"></option>
</select>
<p>選択した色: {selectedColors.join(', ')}</p>

コンポーネントのライフサイクル

Svelteのコンポーネントには、マウント(表示)、更新、アンマウント(非表示)の各段階でコードを実行できるライフサイクル関数があります:

<script>
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
  
  let count = 0;
  
  // コンポーネントがDOMに追加された後に実行
  onMount(() => {
    console.log('コンポーネントがマウントされました');
    
    // タイマーを設定(例)
    const interval = setInterval(() => {
      count += 1;
    }, 1000);
    
    // クリーンアップ関数を返す(任意)
    return () => {
      clearInterval(interval);
    };
  });
  
  // コンポーネントがDOMから削除される前に実行
  onDestroy(() => {
    console.log('コンポーネントが破棄されます');
  });
  
  // DOMが更新される前に実行
  beforeUpdate(() => {
    console.log('DOMが更新される前', count);
  });
  
  // DOMが更新された後に実行
  afterUpdate(() => {
    console.log('DOMが更新された後', count);
  });
</script>

<p>カウント: {count}</p>

特に重要なのはonMountで、外部APIの呼び出しやデータの取得などの初期化処理に使用します。

イベントハンドリング

Svelteのイベント処理は直感的で、簡単に実装できます:

<script>
  function handleClick(event) {
    alert('クリックされました!');
    console.log(event); // DOM イベントオブジェクト
  }
  
  // イベント修飾子の使用例
  function handleKeydown(event) {
    alert(`キーが押されました: ${event.key}`);
  }
</script>

<!-- 基本的なイベントハンドラ -->
<button on:click={handleClick}>クリック</button>

<!-- インラインハンドラ -->
<button on:click={() => alert('インラインハンドラ')}>インラインクリック</button>

<!-- イベント修飾子 -->
<button on:click|once={handleClick}>一度だけクリック</button>
<button on:click|preventDefault={handleClick}>デフォルト動作を防止</button>
<button on:click|stopPropagation={handleClick}>イベント伝播を停止</button>

<!-- キーボードイベント -->
<input on:keydown|preventDefault={handleKeydown}>

<!-- キーボードイベント+特定のキーにのみ反応 -->
<input on:keydown|preventDefault={(e) => e.key === 'Enter' && handleKeydown(e)}>

カスタムイベント

コンポーネント間で通信するために、カスタムイベントを作成・ディスパッチすることもできます:

<!-- ChildComponent.svelte -->
<script>
  import { createEventDispatcher } from 'svelte';
  
  // イベントディスパッチャーを作成
  const dispatch = createEventDispatcher();
  
  // データと共にイベントを発火
  function sendMessage() {
    dispatch('message', {
      text: 'こんにちは、親コンポーネント!',
      timestamp: new Date()
    });
  }
</script>

<button on:click={sendMessage}>
  メッセージを送信
</button>

親コンポーネントでは次のようにイベントをリッスンします:

<!-- ParentComponent.svelte -->
<script>
  import ChildComponent from './ChildComponent.svelte';
  
  let lastMessage = null;
  
  function handleMessage(event) {
    // カスタムイベントからデータを取得
    lastMessage = event.detail;
    console.log('子コンポーネントからメッセージを受信:', lastMessage);
  }
</script>

<ChildComponent on:message={handleMessage} />

{#if lastMessage}
  <div>
    <p>受信したメッセージ: {lastMessage.text}</p>
    <p>タイムスタンプ: {lastMessage.timestamp.toLocaleString()}</p>
  </div>
{/if}

ストアを使用した状態管理

複数のコンポーネント間で状態を共有する場合、Svelteの組み込みストア機能を使用できます:

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

// 書き込み可能なストア
export const count = writable(0);

// 読み取り専用ストア
export const time = readable(new Date(), function start(set) {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);
  
  return function stop() {
    clearInterval(interval);
  };
});

// 派生ストア(他のストアから派生)
export const formattedTime = derived(
  time,
  $time => $time.toLocaleTimeString()
);

コンポーネントでストアを使用する方法:

<script>
  import { count, time, formattedTime } from './store.js';
  
  // ストアの値を更新
  function incrementCount() {
    count.update(n => n + 1);
  }
  
  // ストアを直接設定
  function resetCount() {
    count.set(0);
  }
  
  // ストアの値を取得(subscribe)
  let countValue;
  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });
  
  // コンポーネントが破棄されたときにunsubscribe
  import { onDestroy } from 'svelte';
  onDestroy(unsubscribe);
</script>

<!-- 自動サブスクライブ構文 ($) -->
<h2>カウント: {$count}</h2>
<button on:click={incrementCount}>増加</button>
<button on:click={resetCount}>リセット</button>

<p>現在時刻: {$formattedTime}</p>

Svelteのストアは$接頭辞を使って簡単にアクセスでき、自動的にサブスクライブとアンサブスクライブを処理します。これにより、コンポーネント間で状態を共有する際のコードが大幅に簡略化されます。

次のセクションでは、これらの知識を活かして実践的なUI開発テクニックを学びましょう。

Svelteを使った実践的なUI開発テクニック

ここでは、Svelteを使った実際のUI開発でよく使われるテクニックや、より高度な機能について解説します。実践的な例を通して、Svelteの効果的な活用法を学びましょう。

アニメーションとトランジション

Svelteには、要素の追加・削除時のアニメーションを簡単に実装できる組み込み機能があります。

<script>
  import { fade, fly, slide, scale } from 'svelte/transition';
  import { elasticOut } from 'svelte/easing';
  
  let visible = true;
  
  function toggleVisibility() {
    visible = !visible;
  }
</script>

<button on:click={toggleVisibility}>
  {visible ? '非表示' : '表示'}
</button>

{#if visible}
  <!-- フェードイン/アウト -->
  <div transition:fade={{ duration: 300 }}>
    フェードトランジション
  </div>

  <!-- スライドイン/アウト -->
  <div transition:slide={{ duration: 300 }}>
    スライドトランジション
  </div>
  
  <!-- 複数のトランジションを組み合わせる(in/outで別々に設定) -->
  <div 
    in:fly={{ y: 200, duration: 500 }} 
    out:fade={{ duration: 300 }}
  >
    複合トランジション
  </div>
  
  <!-- イージング関数を使用したカスタムトランジション -->
  <div transition:scale={{ duration: 500, easing: elasticOut }}>
    イージング付きトランジション
  </div>
{/if}

Svelteのトランジションは、JavaScriptとCSSの両方を使ったアニメーションを自動生成するため、非常にスムーズで軽量なアニメーションが実現できます。

スロットを使ったコンポーネントの柔軟な構成

スロットを使うことで、コンポーネントの内容を親コンポーネントから注入できます:

<!-- Card.svelte -->
<script>
  export let title = 'カードタイトル';
</script>

<div class="card">
  <div class="card-header">
    <h3>{title}</h3>
    <!-- 名前付きスロット -->
    <div class="card-actions">
      <slot name="actions"><!-- デフォルトコンテンツ(任意) --></slot>
    </div>
  </div>
  
  <div class="card-body">
    <!-- デフォルトスロット -->
    <slot>カードにコンテンツがありません。</slot>
  </div>
  
  <div class="card-footer">
    <!-- 別の名前付きスロット -->
    <slot name="footer"></slot>
  </div>
</div>

<style>
  .card {
    border: 1px solid #ddd;
    border-radius: 4px;
    overflow: hidden;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    margin-bottom: 1rem;
  }
  
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.5rem 1rem;
    background-color: #f8f9fa;
    border-bottom: 1px solid #ddd;
  }
  
  .card-body {
    padding: 1rem;
  }
  
  .card-footer {
    padding: 0.5rem 1rem;
    border-top: 1px solid #ddd;
    background-color: #f8f9fa;
  }
</style>

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

<script>
  import Card from './Card.svelte';
</script>

<Card title="プロフィールカード">
  <!-- デフォルトスロットにコンテンツを提供 -->
  <p>こんにちは、私はSvelteユーザーです。</p>
  <img src="/avatar.png" alt="プロフィール画像" />
  
  <!-- 名前付きスロットにコンテンツを提供 -->
  <div slot="actions">
    <button>編集</button>
  </div>
  
  <div slot="footer">
    <p>最終更新: 2025年5月14日</p>
  </div>
</Card>

アクション(Actions)を使ったDOM操作

アクションは、特定のDOM要素に対して機能を追加するための仕組みです。例えば、クリックアウトサイド検出やツールチップなどを実装できます:

<script>
  // actions/clickOutside.js
  export function clickOutside(node, onClickOutside) {
    function handleClick(event) {
      if (!node.contains(event.target)) {
        onClickOutside();
      }
    }
    
    document.addEventListener('click', handleClick, true);
    
    return {
      update(newCallback) {
        onClickOutside = newCallback;
      },
      destroy() {
        document.removeEventListener('click', handleClick, true);
      }
    };
  }
</script>

<!-- 使用例 -->
<script>
  import { clickOutside } from './actions/clickOutside.js';
  
  let isOpen = false;
  
  function toggle() {
    isOpen = !isOpen;
  }
  
  function close() {
    isOpen = false;
  }
</script>

<button on:click={toggle}>
  {isOpen ? '閉じる' : '開く'}
</button>

{#if isOpen}
  <div use:clickOutside={close} class="dropdown">
    ドロップダウンメニュー
    <ul>
      <li>項目1</li>
      <li>項目2</li>
      <li>項目3</li>
    </ul>
  </div>
{/if}

コンポーネントの再利用とコンポジション

Svelteでは、小さなコンポーネントを組み合わせて複雑なUIを構築することが推奨されています。以下は、再利用可能なフォームコンポーネントの例です:

<!-- Form.svelte -->
<script>
  export let onSubmit = () => {};
</script>

<form on:submit|preventDefault={onSubmit}>
  <slot></slot>
</form>

<!-- FormField.svelte -->
<script>
  export let label = '';
  export let error = '';
  export let id = '';
</script>

<div class="form-field">
  <label for={id}>{label}</label>
  <slot></slot>
  {#if error}
    <p class="error">{error}</p>
  {/if}
</div>

<style>
  .form-field {
    margin-bottom: 1rem;
  }
  
  label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: bold;
  }
  
  .error {
    color: red;
    font-size: 0.8em;
    margin-top: 0.2rem;
  }
</style>

<!-- ボタングループコンポーネント -->
<!-- ButtonGroup.svelte -->
<div class="button-group">
  <slot></slot>
</div>

<style>
  .button-group {
    display: flex;
    gap: 0.5rem;
    margin-top: 1rem;
  }
</style>

これらのコンポーネントを使ってログインフォームを構築する例:

<!-- LoginForm.svelte -->
<script>
  import Form from './Form.svelte';
  import FormField from './FormField.svelte';
  import ButtonGroup from './ButtonGroup.svelte';
  
  let email = '';
  let password = '';
  let errors = { email: '', password: '' };
  
  function validateForm() {
    let isValid = true;
    errors = { email: '', password: '' };
    
    if (!email) {
      errors.email = 'メールアドレスは必須です';
      isValid = false;
    } else if (!/\S+@\S+\.\S+/.test(email)) {
      errors.email = '有効なメールアドレスを入力してください';
      isValid = false;
    }
    
    if (!password) {
      errors.password = 'パスワードは必須です';
      isValid = false;
    } else if (password.length < 8) {
      errors.password = 'パスワードは8文字以上である必要があります';
      isValid = false;
    }
    
    return isValid;
  }
  
  function handleSubmit() {
    if (validateForm()) {
      // フォーム送信処理
      console.log('送信データ:', { email, password });
      alert('ログイン処理を実行します!');
    }
  }
</script>

<Form onSubmit={handleSubmit}>
  <FormField label="メールアドレス" id="email" error={errors.email}>
    <input 
      id="email"
      type="email" 
      bind:value={email} 
      placeholder="[email protected]"
    />
  </FormField>
  
  <FormField label="パスワード" id="password" error={errors.password}>
    <input 
      id="password"
      type="password" 
      bind:value={password} 
      placeholder="8文字以上のパスワード"
    />
  </FormField>
  
  <ButtonGroup>
    <button type="submit" class="primary">ログイン</button>
    <button type="button" class="secondary">キャンセル</button>
  </ButtonGroup>
</Form>

レスポンシブデザインの実装

Svelteでは、通常のCSSと組み合わせてレスポンシブなUIを簡単に実装できます:

<script>
  import { onMount } from 'svelte';
  
  // レスポンシブな画面サイズを追跡
  let windowWidth;
  let isMobile;
  
  // クライアントサイドでのみ実行
  onMount(() => {
    // 初期値を設定
    updateWindowWidth();
    
    // ウィンドウリサイズ時にサイズを更新
    window.addEventListener('resize', updateWindowWidth);
    
    return () => {
      window.removeEventListener('resize', updateWindowWidth);
    };
  });
  
  function updateWindowWidth() {
    windowWidth = window.innerWidth;
    isMobile = windowWidth < 768; // モバイルサイズの閾値
  }
</script>

<div class="container" class:mobile={isMobile}>
  <div class="sidebar">
    {#if !isMobile}
      <!-- PC用のサイドバーコンテンツ -->
      <nav>
        <ul>
          <li><a href="/">ホーム</a></li>
          <li><a href="/about">概要</a></li>
          <li><a href="/contact">お問い合わせ</a></li>
        </ul>
      </nav>
    {:else}
      <!-- モバイル用のメニュー -->
      <button class="menu-button">メニュー</button>
    {/if}
  </div>
  
  <div class="content">
    <h1>レスポンシブレイアウトの例</h1>
    <p>現在の画面幅: {windowWidth}px</p>
    <p>デバイス: {isMobile ? 'モバイル' : 'PC'}</p>
  </div>
</div>

<style>
  .container {
    display: flex;
    max-width: 1200px;
    margin: 0 auto;
  }
  
  .container.mobile {
    flex-direction: column;
  }
  
  .sidebar {
    padding: 1rem;
    background-color: #f0f0f0;
  }
  
  .content {
    flex: 1;
    padding: 1rem;
  }
  
  /* PC表示時のスタイル */
  .sidebar {
    width: 250px;
  }
  
  /* モバイル表示時のスタイル */
  .container.mobile .sidebar {
    width: 100%;
  }
  
  /* メディアクエリでも制御可能 */
  @media (max-width: 768px) {
    .container {
      flex-direction: column;
    }
    
    .sidebar {
      width: 100%;
    }
  }
</style>

これらの実践的なテクニックを習得することで、Svelteを使った効率的なUI開発が可能になります。次のセクションでは、Svelteでのルーティングとアプリケーション構築方法について見ていきましょう。

Svelteでのルーティングとアプリケーション構築方法

ウェブアプリケーションを構築する上で重要なルーティングとアプリケーション全体の構築方法について見ていきましょう。SvelteKitを使用することで、シームレスなSPA(シングルページアプリケーション)開発が可能です。

SvelteKitでのルーティング

SvelteKitはファイルベースのルーティングを採用しており、ファイル構造そのものがルート構造となります:

src/
  routes/
    +page.svelte       # ルートページ (/)
    about/
      +page.svelte     # Aboutページ (/about)
    blog/
      +page.svelte     # ブログ一覧ページ (/blog)
      [slug]/          # 動的ルートパラメータ
        +page.svelte   # 個別ブログページ (/blog/:slug)

基本的な+page.svelteファイルは、そのルートのビューを定義します:

<!-- src/routes/+page.svelte -->
<script>
  // インポートや変数・関数の定義
</script>

<h1>ホームページ</h1>
<p>ようこそ、SvelteKitアプリケーションへ!</p>

ルートパラメータとデータロード

動的ルートパラメータを使用することで、ブログの記事ページなどを実装できます:

<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  // pageプロパティからデータと動的パラメータを取得
  export let data;
</script>

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

データロードは+page.js(クライアントサイド)または+page.server.js(サーバーサイド)で行います:

// src/routes/blog/[slug]/+page.js
export async function load({ params, fetch }) {
  // paramsからslugを取得
  const { slug } = params;
  
  // APIからデータを取得
  const response = await fetch(`/api/posts/${slug}`);
  const post = await response.json();
  
  return { post };
}

または、サーバーサイドでのデータ取得:

// src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
import { db } from '$lib/database';

export async function load({ params }) {
  const { slug } = params;
  
  // データベースからデータを取得
  const post = await db.getPost(slug);
  
  if (!post) {
    // 404エラーを返す
    throw error(404, 'ブログ記事が見つかりません');
  }
  
  return { post };
}

レイアウトとナビゲーション

共通レイアウトを作成するには、+layout.svelteファイルを使用します:

<!-- src/routes/+layout.svelte -->
<script>
  import Header from '$lib/components/Header.svelte';
  import Footer from '$lib/components/Footer.svelte';
  import '../app.css'; // グローバルスタイル
</script>

<div class="app">
  <Header />
  
  <main>
    <!-- このスロットに各ページのコンテンツが入る -->
    <slot></slot>
  </main>
  
  <Footer />
</div>

<style>
  .app {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
  
  main {
    flex: 1;
    padding: 1rem;
    max-width: 1200px;
    margin: 0 auto;
    width: 100%;
  }
</style>

ナビゲーションはSvelteKitの$app/navigationを使用します:

<!-- src/lib/components/Header.svelte -->
<script>
  import { page } from '$app/stores';
  import { goto } from '$app/navigation';
  
  // 現在のパスを取得
  $: path = $page.url.pathname;
  
  // リンク配列
  const links = [
    { href: '/', label: 'ホーム' },
    { href: '/about', label: '概要' },
    { href: '/blog', label: 'ブログ' },
    { href: '/contact', label: 'お問い合わせ' }
  ];
  
  // プログラムによるナビゲーション例
  function navigateToLogin() {
    goto('/login');
  }
</script>

<header>
  <nav>
    <ul>
      {#each links as link}
        <li>
          <a 
            href={link.href} 
            class:active={path === link.href}
          >
            {link.label}
          </a>
        </li>
      {/each}
    </ul>
  </nav>
  
  <div class="auth-buttons">
    <button on:click={navigateToLogin}>ログイン</button>
  </div>
</header>

<style>
  header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    background-color: #f8f9fa;
    border-bottom: 1px solid #ddd;
  }
  
  nav ul {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
  }
  
  nav li:not(:last-child) {
    margin-right: 1rem;
  }
  
  a {
    text-decoration: none;
    color: #333;
    padding: 0.5rem;
  }
  
  a.active {
    font-weight: bold;
    color: #ff3e00;
  }
</style>

APIルートの作成

SvelteKitでは、APIエンドポイントも簡単に作成できます:

// src/routes/api/posts/[slug]/+server.js
import { json } from '@sveltejs/kit';
import { db } from '$lib/database';

export async function GET({ params }) {
  const { slug } = params;
  
  const post = await db.getPost(slug);
  
  if (!post) {
    // 404エラーを返す
    return new Response('記事が見つかりません', { status: 404 });
  }
  
  // JSONレスポンスを返す
  return json(post);
}

export async function POST({ request, params }) {
  const { slug } = params;
  const data = await request.json();
  
  // 投稿を更新
  const updatedPost = await db.updatePost(slug, data);
  
  return json(updatedPost);
}

フォームの処理

SvelteKitではフォーム処理も簡単に実装できます:

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

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

{#if form?.success}
  <div class="success">
    メッセージを送信しました。お問い合わせありがとうございます!
  </div>
{/if}

<form method="POST" use:enhance>
  <div class="form-group">
    <label for="name">お名前</label>
    <input 
      id="name" 
      name="name" 
      type="text" 
      required
      class:error={form?.errors?.name}
    />
    {#if form?.errors?.name}
      <p class="error-message">{form.errors.name}</p>
    {/if}
  </div>
  
  <div class="form-group">
    <label for="email">メールアドレス</label>
    <input 
      id="email" 
      name="email" 
      type="email" 
      required
      class:error={form?.errors?.email}
    />
    {#if form?.errors?.email}
      <p class="error-message">{form.errors.email}</p>
    {/if}
  </div>
  
  <div class="form-group">
    <label for="message">メッセージ</label>
    <textarea 
      id="message" 
      name="message" 
      rows="5" 
      required
      class:error={form?.errors?.message}
    ></textarea>
    {#if form?.errors?.message}
      <p class="error-message">{form.errors.message}</p>
    {/if}
  </div>
  
  <button type="submit">送信</button>
</form>

そしてサーバーサイドでの処理:

// src/routes/contact/+page.server.js
import { fail } from '@sveltejs/kit';
import { sendEmail } from '$lib/email';

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');
    
    // バリデーション
    const errors = {};
    
    if (!name || name.length < 2) {
      errors.name = 'お名前は2文字以上で入力してください';
    }
    
    if (!email || !email.includes('@')) {
      errors.email = '有効なメールアドレスを入力してください';
    }
    
    if (!message || message.length < 10) {
      errors.message = 'メッセージは10文字以上で入力してください';
    }
    
    // エラーがある場合は早期リターン
    if (Object.keys(errors).length > 0) {
      return fail(400, { errors });
    }
    
    try {
      // メール送信処理(実際の実装はライブラリに依存)
      await sendEmail({
        to: '[email protected]',
        subject: `お問い合わせ: ${name}様より`,
        body: `
          名前: ${name}
          メールアドレス: ${email}
          
          ${message}
        `
      });
      
      // 成功レスポンス
      return {
        success: true
      };
    } catch (error) {
      console.error('メール送信エラー:', error);
      return fail(500, {
        message: 'メール送信に失敗しました。後でもう一度お試しください。'
      });
    }
  }
};

認証と保護されたルート

SvelteKitでユーザー認証を実装するには:

// src/hooks.server.js
import { redirect } from '@sveltejs/kit';

// 保護されたルートのリスト
const protectedRoutes = [
  '/dashboard',
  '/profile',
  '/settings'
];

export async function handle({ event, resolve }) {
  // セッションからユーザー情報を取得
  const session = await event.locals.getSession();
  
  // ユーザー情報をlocalsに格納
  event.locals.user = session?.user;
  
  // 保護されたルートにアクセスしようとしていてログインしていない場合
  if (
    protectedRoutes.some(route => event.url.pathname.startsWith(route)) &&
    !event.locals.user
  ) {
    // ログインページにリダイレクト
    throw redirect(303, `/login?redirectTo=${event.url.pathname}`);
  }
  
  // 通常の処理を続行
  return resolve(event);
}

// 各リクエストの処理前に実行
export function getSession(event) {
  return {
    user: event.locals.user
  };
}

デプロイ

SvelteKitアプリケーションは様々な方法でデプロイできます:

  1. 静的サイト生成(SSG)
// 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: true
    })
  }
};

export default config;
  1. Node.jsサーバー
// svelte.config.js
import adapter from '@sveltejs/adapter-node';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter()
  }
};

export default config;
  1. Vercel, Netlify, Cloudflare Pagesなどのサービス
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel'; // または adapter-netlify など

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter()
  }
};

export default config;

まとめ

SvelteとSvelteKitを使用することで、モダンで高速なWebアプリケーションを効率的に開発できます。ビルド時コンパイルの利点により、少ないコード量でパフォーマンスの高いアプリケーションが構築可能です。

この記事では、Svelteの基本から実践的な応用まで幅広く解説しました。これらの知識を活用して、ぜひSvelteでのWebアプリケーション開発にチャレンジしてみてください。Webフロントエンド開発の新たな選択肢として、Svelteの可能性を実感いただけるはずです。

また、さらに学習を進めたい方は、公式ドキュメントやチュートリアルも参考になるでしょう。Svelteコミュニティも活発で、多くのライブラリやリソースが日々増えています。

Happy Svelting!

TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

おすすめの書籍

おすすめコンテンツ