10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Svelte初心者】SvelteKitで開発を始めて躓いた3つのポイント

Last updated at Posted at 2025-12-01

この記事は CAMPFIRE Advent Calendar 2025 2日目の参加記事です。

はじめに

SvelteKit でコミュニティ機能を提供するWebアプリケーションの開発を始めたばかりの頃、いくつかのポイントで悩んだ経験を共有します。同じように迷っている方の参考になれば幸いです。

使用技術スタック

本記事で使用しているmagma-webプロジェクトの技術スタックは以下の通りです:

  • フロントエンド: SvelteKit 2.16.0 (Svelte 5.0.0)
  • ビルドツール: Vite 6.2.6

前提条件

本記事では、以下の前提で開発を行っています:

  • Vue.jsなどjsのフレームワークの経験は多少ある状態で開始
  • SSR = false: サーバーサイドレンダリングは無効化
  • クライアントサイドのみで動作: ブラウザ上でのみ実行される

本記事では、以下の3つのポイントについて、実際に躓いた経験と解決策を紹介します:

  1. Svelteの基本構造とロジックの配置
  2. 記号など省略記号を使った処理の意味
  3. 受け渡しをどうするか(Context vs Props)

1. Svelteの基本構造とロジックの配置で迷った話

SvelteKitでは、ファイルベースのルーティングシステムが採用されています。最初は、どのファイルに何を書くべきか、ロジックをどこに置くべきかで迷いました。

SvelteKitの基本構造

SvelteKitでは、src/routesディレクトリ配下のファイル構造がそのままURLのルーティングになります。

src/routes/
├── +page.svelte          # ページコンポーネント
├── +layout.svelte        # レイアウトコンポーネント
├── (main)/               # ルートグループ
│   ├── +layout.svelte
│   └── users/
│       ├── +page.svelte
│       └── [userId]/
│           ├── +layout.svelte
│           └── +page.svelte
└── (user)/
    └── users/
        └── [userId]/
            └── products/
                └── [productId]/
                    └── +page.svelte

ファイルパスとURLの対応関係

SvelteKitでは、ファイルパスがそのままURLになります。[userId][productId]のような角括弧は動的パラメータを表します。

また、(main)(user)のように括弧で囲まれたディレクトリはルートグループと呼ばれ、URLには反映されません。これは、ルーティングを整理するためのまとめ的な意味で使用されます。

ファイルパス URLの例
src/routes/users/[userId]/+page.svelte /users/123 123userIdパラメータ
src/routes/(main)/users/[userId]/+page.svelte /users/123 (main)はURLに反映されない
src/routes/(user)/users/[userId]/products/[productId]/+page.svelte /users/123/products/456 パラメータが複数の場合も可能

躓きポイント

  • 階層と記号が多くてURLと修正ファイルが一致させるまで時間がかかる: SvelteKitのファイルベースルーティングでは、ディレクトリ構造がそのままURLになるため、どのファイルを修正すべきか判断するのに時間がかかりました
  • +page.svelte+layout.svelteの違いがわからない: どちらに何を書くべきか迷いました
    • +page.svelte: そのルートのページコンテンツ
    • +layout.svelte: そのルートと子ルートで共通のレイアウト
  • ロジックをどこに置くべきかわからない: データ取得や状態管理のロジックを+page.svelteに直接書くか、+layout.svelteに書くか、別のコンポーネントファイルに分けるか迷いました

すべてを+page.svelteに書くと、ページコンポーネントが肥大化し、ロジックと表示が混在して再利用が難しくなるのが予測される。そのため、+page.svelte+layout.svelteにはビューの処理だけを記載し、ロジック(データ取得など)は別ファイルに分ける方針にしました。


2. 記号など省略記号を使った処理の意味がわからない

Svelte 5では、リアクティビティシステムが大幅に変更され、従来の$:(リアクティブステートメント)に加えて、$effect$state$derived$propsなどの新しいAPIが導入されました。

補足: Side Effectとは?
Side Effectとは、関数や式の実行によって、その関数や式の戻り値以外に影響を与える処理のことです。具体的には以下のような処理が該当します:

  • DOM操作(要素のフォーカス、スクロールなど)
  • API呼び出し(サーバーへのリクエスト)
  • タイマーの設定(setIntervalsetTimeoutなど)
  • イベントリスナーの登録
  • ログ出力(console.logなど)

躓きポイント

$:$effectは違うもの?

スベルトのついて調べていると$: を利用しているコードがいくつか目に入りました。
$:は以前からあった機能で、Svelte 5では$derived$effectが推奨されています。

<script>
  let count = 0;
  
  // 従来の方法(legacy mode)
  $: {
    console.log('count changed:', count);
  }
  $: doubled = count * 2;
</script>

Svelte 5のrunes modeでは、以下のように書き換えられます:

<script>
  let count = $state(0);
  
  // $derived: 派生状態(計算された値)
  const doubled = $derived(count * 2);
  
  // $effect: Side Effectの処理(DOM操作、API呼び出しなど)
  $effect(() => {
    console.log('count changed:', count);
  });
</script>
$propsexport letの違いがわからない

Svelte 5では$props()が推奨されています。$props()を使うことで、型安全性が向上し、デフォルト値の設定も簡単になります。

<script>
  export let userId: string;  // 従来の方法
  let { userId }: Props = $props();  // Svelte 5の方法
</script>

3. 受け渡しをどうするか(Context vs Props)で悩んだ話

Webアプリケーションの開発中、現在のユーザー情報、商品情報、認証状態、カートの状態などを管理する必要がありました。各コンポーネントで個別に状態を管理していたため、グローバルな状態が一元管理されず、同じデータを複数の場所で管理し、データの整合性が取れない問題が発生していました。

躓きポイント

各コンポーネントで個別に状態を管理していたため、以下の問題が発生していました:

  • 状態が散在し、同じデータを複数の場所で管理している
  • データの重複取得が発生し、同じAPIを複数回呼び出している
  • 状態の更新がバラバラで、他のコンポーネントに反映されない
  • データの受け渡し方法が統一されていない(Props、Context、Store、直接API呼び出しなどが混在)

この状態では、データの流れが追いにくく、バグの原因になりやすいと感じました。

試行錯誤と学び

最初はすべてをPropsで受け渡していましたが、Propsのバケツリレーが発生し、深い階層のコンポーネントに渡すのが大変でした。次にすべてをContextで管理しようとしましたが、Contextが多くなりすぎて管理が難しくなりました。Storeで管理することも検討しましたが、Store間の依存関係が複雑になる問題がありました。

// lib/context/user-context.ts
import { setContext, getContext } from 'svelte';
import type { Writable } from 'svelte/store';

const USER_CONTEXT_KEY = Symbol('user');

export function setUserContext(user: Writable<User>) {
  setContext(USER_CONTEXT_KEY, user);
}

export function getUserContext(): Writable<User> {
  return getContext(USER_CONTEXT_KEY);
}
<!-- src/routes/(main)/+layout.svelte -->
<script>
  import { setUserContext } from '$lib/context/user-context';
  import { useQuery } from '@tanstack/svelte-query';
  import { writable } from 'svelte/store';
  
  // データ取得
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: () => getUsers(),
  });
  
  // Contextに設定
  if (users) {
    const userStore = writable(users[0]);
    setUserContext(userStore);
  }
</script>

<slot />

結論

  • Contextは文脈ごとに分ける: User、Product、Cartなど、文脈ごとにContextを分け、関連するContextは同じファイルにまとめる
  • ContextとPropsの使い分け: Contextは複数のコンポーネントで共有するデータ、Propsは特定のコンポーネントに渡すデータ
  • レイアウトでContextを設定: ルーティングに応じて適切なレイアウトでContextを設定することで、データの流れが明確になる
  • 一貫した方針の重要性: 一貫した方針を設けることで、コードの可読性が向上し、後から見返した際も理解しやすくなる
  • 過剰な抽象化を避ける: グローバルな状態管理ライブラリは必要になるまで使わず、SvelteKitの標準機能で十分な場合が多い

まとめ

SvelteKitで開発を始めたばかりの頃、以下の4つのポイントで躓きました:

  1. Svelteの基本構造とロジックの配置

    • ファイルベースルーティングでは、ディレクトリ構造がそのままURLになるため、どのファイルを修正すべきか判断するのに時間がかかった
    • +page.svelte+layout.svelteの使い分けが重要(+page.svelteはページコンテンツ、+layout.svelteは共通レイアウト)
    • ロジックと表示を適切に分離することで、コードの可読性が向上し、再利用が容易になる
  2. 記号など省略記号を使った処理の意味

    • Svelte 5では、従来の$:(リアクティブステートメント)に加えて、$effect$state$derived$propsなどの新しいAPIが導入された
    • Svelte 5のrunes modeでは$derived$effectが推奨される($:はlegacy mode)
    • $derivedは派生状態(計算された値)、$effectはSide Effectの処理に使い分ける
    • $props()export letよりも型安全性が向上し、デフォルト値の設定も簡単になる
    • ストアは$page$userStoreのように$プレフィックスで自動購読できる
  3. 受け渡しをどうするか(Context vs Props)

    • すべてをPropsで受け渡すとPropsのバケツリレーが発生し、すべてをContextで管理するとContextが多くなりすぎて管理が難しくなる
    • Contextを文脈ごとに分け、レイアウトでContextを設定する方針で実装することで、データの流れが明確になる
    • Contextは複数のコンポーネントで共有するデータ、Propsは特定のコンポーネントに渡すデータとして使い分ける
    • 一貫した方針を設けることで、コードの可読性が向上し、過剰な抽象化を避けることができる

どのポイントも、一貫した方針を設けることが重要だと感じました。

まだまだ勉強中の身ですが、同じように迷っている方の参考になれば幸いです。

参考資料

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?