$appモジュール

SvelteKitは$app/というプレフィックスを持つ組み込みモジュール群を提供しています。これらはSvelteKitアプリケーション内でのみ使用可能な特別なモジュールです。

モジュール一覧

ダイアグラムを読み込み中...

$app/state (Svelte 5推奨)

Svelte 5のルーンと連携するリアクティブ状態管理 - SvelteKit 2.12+で導入された、Runesシステムとより自然に統合される新しいモジュール。

利用可能な状態

import {
  page,        // 現在のページ情報
  navigating,  // ナビゲーション状態
  updated      // アプリ更新状態
} from '$app/state';

// 直接アクセス可能($プレフィックス不要)
console.log(page.url.pathname);
console.log(page.params.id);
typescript

$app/stores (レガシー)

従来のストアベースの状態管理 - Svelte 4互換性のために残されているAPI。新規プロジェクトでは$app/stateの使用を推奨。

利用可能なストア

import {
  page,        // 現在のページ情報
  navigating,  // ナビゲーション状態
  updated,     // アプリ更新状態
  getStores    // ストアの直接取得
} from '$app/stores';

// ストアの$プレフィックスでアクセス
console.log($page.url.pathname);
console.log($page.params.id);
typescript

page - 現在のページ情報

$app/state での使用(推奨)

<script lang="ts">
  import { page } from '$app/state';
</script>

<!-- 直接アクセス -->
<p>現在のURL: {page.url.pathname}</p>
<p>クエリパラメータ: {page.url.searchParams.toString()}</p>
<p>ルートID: {page.route.id}</p>

{#if page.error}
  <p>エラー: {page.error.message}</p>
{/if}
svelte

$app/stores での使用(レガシー)

<script lang="ts">
  import { page } from '$app/stores';
</script>

<!-- $プレフィックスでアクセス -->
<p>現在のURL: {$page.url.pathname}</p>
<p>クエリパラメータ: {$page.url.searchParams.toString()}</p>
<p>ルートID: {$page.route.id}</p>

{#if $page.error}
  <p>エラー: {$page.error.message}</p>
{/if}
svelte

pageのプロパティ

interface Page {
  url: URL;           // 現在のURL
  params: Record<string, string>; // ルートパラメータ
  route: {
    id: string | null; // ルートID(例: '/blog/[slug]')
  };
  status: number;     // HTTPステータスコード
  error: App.Error | null; // エラー情報
  data: App.PageData; // load関数の結果
  form: any;          // form actionsの結果
  state: App.PageState; // History APIの状態
}
typescript

navigating - ナビゲーション状態

現在のナビゲーション状態を表します。ナビゲーション中は値を持ち、完了後はnullになります。

$app/state での使用(推奨)

<script lang="ts">
  import { navigating } from '$app/state';
</script>

{#if navigating}
  <div class="loading">
    <p>Loading...</p>
    <p>From: {navigating.from?.url.pathname}</p>
    <p>To: {navigating.to?.url.pathname}</p>
    <p>Type: {navigating.type}</p>
  </div>
{/if}
svelte

$app/stores での使用(レガシー)

<script lang="ts">
  import { navigating } from '$app/stores';
</script>

{#if $navigating}
  <div class="loading">
    <p>Loading...</p>
    <p>From: {$navigating.from?.url.pathname}</p>
    <p>To: {$navigating.to?.url.pathname}</p>
    <p>Type: {$navigating.type}</p>
  </div>
{/if}
svelte

navigatingのプロパティ

interface Navigation {
  from: { 
    params: Record<string, string>;
    route: { id: string | null };
    url: URL;
  } | null;
  to: {
    params: Record<string, string>; 
    route: { id: string | null };
    url: URL;
  } | null;
  type: 'popstate' | 'link' | 'goto' | 'enter' | 'leave';
  willUnload: boolean;
  delta?: number;
  complete: Promise<void>;
}
typescript

updated - アプリ更新状態

アプリケーションの新しいバージョンが利用可能かを示します。

$app/state での使用(推奨)

<script lang="ts">
  import { updated } from '$app/state';

  $effect(() => {
    if (updated) {
      alert('新しいバージョンが利用可能です。リロードしてください。');
    }
  });
</script>
svelte

$app/stores での使用(レガシー)

<script lang="ts">
  import { updated } from '$app/stores';
  
  $effect(() => {
    if ($updated) {
      alert('新しいバージョンが利用可能です。リロードしてください。');
    }
  });
</script>
svelte

$app/navigation

プログラマティックなナビゲーション機能 - JavaScriptからのページ遷移制御。

利用可能な関数

import {
  goto,              // ページ遷移
  invalidate,        // データ再取得
  invalidateAll,     // 全データ再取得
  preloadCode,       // コードの事前読み込み
  preloadData,       // データの事前読み込み
  afterNavigate,     // ナビゲーション後フック
  beforeNavigate,    // ナビゲーション前フック
  onNavigate,        // View Transitions API
  disableScrollHandling, // スクロール制御の無効化
  pushState,         // History APIのpushState
  replaceState       // History APIのreplaceState
} from '$app/navigation';
typescript

goto - ページ遷移

// 基本的な使い方
await goto('/about');

// オプション付き
await goto('/products', {
  replaceState: true,     // 履歴を置き換え
  noScroll: true,         // スクロールしない
  keepFocus: true,        // フォーカスを維持
  invalidateAll: true     // 全データを再取得
});

// 外部URLへの遷移(window.location)
await goto('https://example.com');
typescript

invalidate - 特定データの再取得

// URLパターンで無効化
await invalidate('/api/user');

// 関数で条件指定
await invalidate((url) => url.pathname.startsWith('/api/'));

// load関数内でdependsと組み合わせ
export const load = async ({ depends }) => {
  depends('app:user');
  return { user: await getUser() };
};

// コンポーネントから無効化
await invalidate('app:user');
typescript

ナビゲーションフック

// ナビゲーション前の処理
beforeNavigate(({ from, to, cancel }) => {
  if (hasUnsavedChanges && !confirm('変更を破棄しますか?')) {
    cancel();
  }
});

// ナビゲーション後の処理
afterNavigate(({ from, to, type }) => {
  // Google Analyticsなどにページビューを送信
  gtag('event', 'page_view', {
    page_path: to?.url.pathname
  });
});

// View Transitions API(実験的)
onNavigate((navigation) => {
  if (!document.startViewTransition) return;
  
  return new Promise((resolve) => {
    document.startViewTransition(async () => {
      resolve();
      await navigation.complete;
    });
  });
});
typescript

$app/environment

実行環境の判定 - ブラウザかサーバーか、開発環境かプロダクションかを判定。

import {
  browser,     // ブラウザで実行中か
  building,    // ビルド中か
  dev,         // 開発モード
  version      // アプリのバージョン
} from '$app/environment';
typescript

使用例

実際のアプリケーションでは、これらの環境変数を組み合わせて、適切な処理の分岐を行います。以下の例では、ブラウザ環境でのみCanvas要素をレンダリングし、SSR時には代替画像を表示しています。

<script lang="ts">
  import { browser, dev } from '$app/environment';
  import { onMount } from 'svelte';
  
  // ブラウザでのみ実行
  if (browser) {
    // localStorage、window、documentなどにアクセス可能
    const theme = localStorage.getItem('theme');
  }
  
  // 開発環境でのみデバッグ情報を表示
  if (dev) {
    console.log('Debug mode enabled');
  }
</script>

{#if browser}
  <!-- クライアントサイドでのみレンダリング -->
  <canvas></canvas>
{:else}
  <!-- SSR時のフォールバック -->
  <img src="/placeholder.png" alt="Canvas placeholder" />
{/if}
svelte

$app/paths

パス管理 - ベースパスとアセットパスの管理。

import {
  base,     // アプリのベースパス
  assets    // アセットのベースパス
} from '$app/paths';
typescript

使用例

パスの値は、リンクや画像、スタイルシートなどのURLを構築する際に使用します。これにより、デプロイ環境に依存しない柔軟なアプリケーションを構築できます。

<script lang="ts">
  import { base, assets } from '$app/paths';
</script>

<!-- ベースパスを考慮したリンク -->
<a href="{base}/about">About</a>
<a href="{base}/products">Products</a>

<!-- アセットパス -->
<img src="{assets}/images/logo.png" alt="Logo" />
<link rel="stylesheet" href="{assets}/styles/global.css" />
svelte

設定例

svelte.config.js:

export default {
  kit: {
    paths: {
      base: '/my-app',        // サブディレクトリでホスト
      assets: 'https://cdn.example.com' // CDNからアセット配信
    }
  }
};
javascript

$app/forms

Progressive Enhancement - JavaScriptなしでも動作するフォーム拡張。

import {
  enhance,        // フォームの拡張
  applyAction,    // アクション結果の適用
  deserialize     // FormDataのデシリアライズ
} from '$app/forms';
typescript

enhance - フォームの拡張

<script lang="ts">
  import { enhance } from '$app/forms';
  
  let loading = false;
</script>

<form
  method="POST"
  use:enhance={({
    formElement,
    formData,
    action,
    cancel,
    submitter
  }) => {
    // 送信前の処理
    loading = true;
    
    // バリデーション
    if (!formData.get('email')) {
      cancel();
      alert('メールアドレスは必須です');
      return;
    }
    
    // 送信後の処理を返す
    return async ({ result, update }) => {
      loading = false;
      
      if (result.type === 'success') {
        // 成功時の処理
        alert('送信完了');
      }
      
      // デフォルトの動作を実行
      await update();
    };
  }}
>
  <input type="email" name="email" required />
  <button disabled={loading}>
    {loading ? '送信中...' : '送信'}
  </button>
</form>
svelte

型定義とカスタマイズ

app.d.ts での型拡張

// src/app.d.ts
declare global {
  namespace App {
    interface Error {
      code?: string;
      details?: unknown;
    }
    
    interface Locals {
      user?: {
        id: string;
        name: string;
        role: 'admin' | 'user';
      };
    }
    
    interface PageData {
      // ページデータの型を定義
    }
    
    interface PageState {
      // History API状態の型を定義
    }
    
    interface Platform {
      // プラットフォーム固有の型
    }
  }
}

export {};
typescript

よくあるパターン

認証状態の管理

// stores/auth.svelte.ts
import { page } from '$app/stores';
import { derived } from 'svelte/store';

export const user = derived(page, ($page) => $page.data.user);
export const isAuthenticated = derived(user, ($user) => !!$user);
typescript

ローディング表示

<script lang="ts">
  import { navigating } from '$app/stores';
  
  let progress = $state(0);
  
  $effect(() => {
    if ($navigating) {
      // プログレスバーのシミュレーション
      progress = 0;
      const interval = setInterval(() => {
        progress = Math.min(progress + 10, 90);
      }, 100);
      
      return () => {
        clearInterval(interval);
        progress = 100;
        setTimeout(() => progress = 0, 200);
      };
    }
  });
</script>

{#if progress > 0}
  <div class="progress-bar" style="width: {progress}%" />
{/if}
svelte

現在ページのハイライト

$app/storesを使用(従来の方法)

<script lang="ts">
  import { page } from '$app/stores';

  const links = [
    { href: '/', label: 'Home' },
    { href: '/about', label: 'About' },
    { href: '/contact', label: 'Contact' }
  ];
</script>

<nav>
  {#each links as link}
    <a
      href={link.href}
      class:active={$page.url.pathname === link.href}
    >
      {link.label}
    </a>
  {/each}
</nav>

<style>
  .active {
    font-weight: bold;
    color: var(--primary);
  }
</style>
svelte

$app/stateを使用(Svelte 5推奨)

<script lang="ts">
  import { page } from '$app/state';

  const links = [
    { href: '/', label: 'Home' },
    { href: '/about', label: 'About' },
    { href: '/contact', label: 'Contact' }
  ];

  // $derivedを使用してリアクティブな値を作成
  let currentPath = $derived(page.url.pathname);

  // または、直接比較する関数を作成
  function isActive(href: string) {
    return page.url.pathname === href;
  }
</script>

<nav>
  {#each links as link}
    <!-- 方法1: $derivedを使用 -->
    <a
      href={link.href}
      class:active={currentPath === link.href}
    >
      {link.label}
    </a>

    <!-- 方法2: 関数を使用 -->
    <a
      href={link.href}
      class:active={isActive(link.href)}
    >
      {link.label}
    </a>
  {/each}
</nav>
svelte

注意事項

サーバーサイドでの使用制限

$app/stores$app/navigationの多くの機能はブラウザでのみ動作します。サーバーサイドで使用する場合はbrowserチェックが必要です。

インポートパスの自動補完

VS Codeなどのエディタでは、$app/で始まるインポートは自動補完されます。TypeScript設定が正しければ型情報も提供されます。

まとめ

SvelteKitの$appモジュール群は、

  • 統一されたAPI: アプリケーション全体で使える標準化されたインターフェース
  • 型安全: TypeScriptによる完全な型サポート
  • リアクティブ: Svelteのストアシステムと統合
  • Progressive Enhancement: JavaScriptが無効でも動作

これらのモジュールを適切に活用することで、よりネイティブなSvelteKitアプリケーションを構築できます。

新しいモジュール (SvelteKit 2.x)

$app/state (2.12+)

Svelte 5のルーンと連携する新しいリアクティブ状態管理モジュールです。$app/storesと同じ情報を提供しますが、Svelte 5のルーンとより自然に統合されます。

基本的な使い方

import { page, navigating, updated } from '$app/state';

// 直接アクセス(.currentは不要)
console.log(page.url.pathname);
console.log(page.params.id);
typescript

$app/storesとの主な違い

1. アクセス方法の違い
<!-- $app/stores: ストアの$プレフィックスが必要 -->
<script lang="ts">
  import { page } from '$app/stores';
</script>
<p>現在: {$page.url.pathname}</p>

<!-- $app/state: 直接アクセス -->
<script lang="ts">
  import { page } from '$app/state';
</script>
<p>現在: {page.url.pathname}</p>
svelte
2. リアクティビティの違い
// $app/stores: $:またはsubscribe
import { page } from '$app/stores';
$: currentPath = $page.url.pathname;

// $app/state: $derivedを使用
import { page } from '$app/state';
let currentPath = $derived(page.url.pathname);
typescript
3. エフェクトでの使用
// $app/stores
import { page } from '$app/stores';
$effect(() => {
  console.log($page.url.pathname);
});

// $app/state
import { page } from '$app/state';
$effect(() => {
  console.log(page.url.pathname);
});
typescript

実践的な例:動的なメタタグ更新

<script lang="ts">
  import { page } from '$app/state';

  // ページタイトルを動的に生成
  let pageTitle = $derived(
    page.url.pathname === '/'
      ? 'ホーム'
      : `${page.url.pathname.slice(1)} - サイト名`
  );

  // メタディスクリプションを動的に生成
  let description = $derived.by(() => {
    const path = page.url.pathname;
    if (path === '/') return 'サイトのトップページです';
    if (path.startsWith('/blog/')) return 'ブログ記事のページです';
    return '詳細ページです';
  });
</script>

<svelte:head>
  <title>{pageTitle}</title>
  <meta name="description" content={description} />
</svelte:head>
svelte
移行のポイント

$app/storesから$app/stateへの移行は、主に以下の変更が必要です。

  1. $プレフィックスを削除
  2. リアクティブな値には$derivedを使用
  3. ストアのサブスクリプションは不要

$app/server (2.27+, 実験的)

リモート関数機能を提供する新しいモジュールです。クライアントからサーバー関数を型安全に呼び出すことができます。

import { query, command } from '$app/server';

// サーバーでのみ実行されるクエリ
 export const getWeather = query(async () => {
  const data = await fetchWeatherFromAPI();
  return data;
});

// クライアントから呼び出し
const weather = await getWeather();
typescript
実験的機能

$app/serverは実験的機能で、設定で明示的に有効化する必要があります。

次のステップ

Last update at: 2025/09/17 09:46:35