Svelte 5 静的サイト専用(ルーン) は何をしますか?
このスキルの対象
「静的専用」とは、成果物がプリビルドされた HTML/CSS/JS のフォルダであり、リクエスト時に Node/エッジサーバーが一切不要 であることを意味します。GitHub Pages、Netlify/Cloudflare/Vercel の 静的 ホスティング、S3+CloudFront、またはローカルファイルとして直接開くことが可能です。この制約により、+page.server.js、+server.js、フォームアクション、hooks.server.js、およびサーバーが動作していることを前提とするコードパスはすべて除外されます。「ルーン」とは、コンポーネントのリアクティビティが Svelte 5 の $state/$derived/$effect/$props プリミティブで記述されており、Svelte 4 の export let / $: / 暗黙のリアクティビティではないことを意味します。
これら2つの制約は相互に作用します。つまり、静的なビルドが正常に機能するためには、$effect 内を含め、どのコードもリクエストごとにサーバー上で動作することを前提にしていてはなりません。この点を常に念頭に置いてください。SvelteKit アプリを adapter-static に移行する際に発生するバグのほとんどは、ここが原因です。
ステップ 1 — 適切なプロジェクト形状を選択する
アプリにファイルベースのルーティング、複数のプリレンダリングページ、または SEO に適したルートごとの HTML が必要かどうかを尋ねる(またはコンテキストから推測する)。これによって全体のセットアップが決定されるため、飛ばしてはいけません。
| ニーズ | 使用するもの |
|---|---|
| 単一ページ / ダッシュボード / ウィジェット / 埋め込み、ルーティング不要 | プレーンな Vite + Svelte (npm create vite@latest -- --template svelte-ts)。最もシンプルな静的専用セットアップ — 誤ってサーバーを導入する可能性がありません。 |
| 複数のルートがあり、それぞれが個別の HTML ファイルにプリレンダリングされ、SEO が重要 | SvelteKit + @sveltejs/adapter-static(prerender = true を設定)。 |
| 複数のルートがあるが、SEO は重要でない(内部ツール、Chrome 拡張機能、Electron/Tauri UI) | SvelteKit + adapter-static の SPA モード(単一の fallback: 'index.html' シェル、クライアントサイドルーティング)。 |
特にルーティングが必要でない限り、デフォルトではプレーンな Vite+Svelte を使用します。設定を誤る可能性が低いためです。両方のパスに対する完全なセットアップ手順、設定ファイル、およびデプロイに関する注意事項は references/static-build.md にあります。svelte.config.js / vite.config.js を記憶から生成する前に必ず読んでください。prerender/adapter のオプションは Svelte のバージョンによって変更され、微妙に間違えやすいからです(例:trailingSlash を忘れる、プリレンダリングしたいページに ssr = false を設定してしまい、実際の HTML の代わりに空のシェルが生成される)。
ステップ 2 — ルーンを使ってコンポーネントを記述する(Svelte 4 構文は使わない)
新しい Svelte 5 のコードでは、export let、$:、on:click、またはベアの writable() ストアをコンポーネント状態を管理する 主要な 方法として決して記述しないでください。これらはほとんどの設定でまだコンパイルされますが、レガシーパターンであり、ここで求められているのはルーンです。コアとなる対応関係は次のとおりです:
| Svelte 4 | Svelte 5 |
|---|---|
let count = 0(トップレベルなのでリアクティブ) | let count = $state(0) |
$: doubled = count * 2 | let doubled = $derived(count * 2)(または複数ステートメントのロジックには $derived.by(() => ...)) |
$: { sideEffect(count) } | $effect(() => { sideEffect(count) }) |
export let name | let { name } = $props() |
export let value; bind:value(子が値を変更) | let { value = $bindable() } = $props() |
onMount/onDestroy(DOM のみのセットアップ/クリーンアップ) | $effect(() => { ...; return () => cleanup(); }) |
<slot name="header"> | {#snippet header()}...{/snippet} + {@render header()} |
writable()/readable() ストア、コンポーネント内で $store | .svelte.js/.svelte.ts モジュール内の $state(...) を直接インポート(リアクティビティがコンポーネント外でも機能) |
重要なルーンコードを書く前に references/runes.md を読んでください。人々が確実に間違える部分をカバーしています:$derived は純粋でなければならない(副作用や内部での状態変更は不可)、$effect は決して読み取った状態に書き込んではならない(無限ループ)、$state.raw(大きなデータや frozen データをミューテートではなく再代入する場合)、$effect.root/untrack(コンポーネントライフサイクル外で状態を共有する場合)、およびコンポーネント間/モジュール間状態のための正確なクラスベースのパターン。これらを間違えると、奇妙な再レンダリングループや古い UI としてのみ現れるバグが発生し、後から診断するのが困難です。最初から正しく記述する方が良いでしょう。
既存の Svelte 4 コードを移行する場合、npx sv migrate svelte-5 が機械的な書き換えを行います(これが現在のツールです。古いドキュメントでは svelte-migrate を参照している場合があります)。ただし、常に差分をレビューしてください。自動マイグレーションでは、ストアをクロスモジュール状態として使用するパターンや、派生値であるべきエフェクトが確実に修正されるとは限りません。
ステップ 3 — セキュリティチェックリストを適用する
静的ホスティングは「セキュリティ面が無い」ことを意味しません。サーバーサイドの攻撃ベクトルは排除されますが、クライアントサイドの攻撃ベクトル(XSS、シークレット漏洩、安全でないサードパーティ埋め込み)は、それをキャッチするサーバーサイドのレイヤーが存在しないため、完全にあなた自身の責任となります。ユーザー生成コンテンツ、{@html}、環境変数、またはサードパーティスクリプトに触れるタスクを完了する前に、references/security.md を確認してください。最も影響度の高いルールを以下に示します。簡単なタスクでも見逃さないようにしてください:
- 無害化されていないコンテンツを
{@html}に渡さないでください。 Svelte は{expression}括弧内のすべてを自動的にエスケープしますが、{@html ...}はそれを完全にバイパスします — これは Svelte アプリにおける最大の XSS 原因です。レンダリングの直前に DOMPurify(または厳格な許可リスト)でサニタイズしてください。パイプラインの前の段階でサニタイズすると、後続の編集でバイパスされる可能性があります。 - 実際のシークレットを
VITE_プレフィックス(または SvelteKit のPUBLIC_プレフィックス)付きの環境変数の背後に絶対に配置しないでください。 Vite はこれらをビルド時に出荷される JS バンドルにインライン化します。誰でもビルドファイルから読み取ることができます。静的専用アプリにはシークレットを隠すサーバーがまったく存在しないため、アプリが実際の API キーを必要とする場合、そのキーはサードパーティサービスのサーバー上、または別途デプロイする別のサーバーレス関数/プロキシの背後に属します。静かにキーをクライアントコードに配線するのではなく、この点を私に指摘してください。 - ヘッダーを送信するサーバーがなくても、Content-Security-Policy を設定してください。 静的ホストは、
app.html/index.html内の<meta http-equiv="Content-Security-Policy">タグ、またはホスト固有のヘッダーファイル(Netlify_headers、Cloudflare Pages_headers、Vercelvercel.jsonヘッダー)を介してこれを引き続きサポートします。詳細はreferences/security.mdを参照してください。 - すべての動的な
href/srcは、ユーザー入力や API レスポンスから取得される可能性がある場合、攻撃者によって制御されているものとして扱ってください。 Svelte はhrefバインディング内のjavascript:URL をブロックしません。スキームを自分で検証/許可リスト化してください。 - 出荷前に
npm audit(または同等のもの)を実行し、ロックファイルを固定してください。 静的サイトは依然としてすべての依存関係のコードをブラウザに直接出荷するため、侵害された依存関係は直接的なクライアントサイドの侵害となります。
参照ファイル
references/runes.md— 完全なルーンごとのガイド。正しい/間違ったパターン、クラスベースの共有状態、プロップス/スニペットの TypeScript 型付け、および詳細な Svelte-4-to-5 移行テーブル。references/static-build.md— プレーンな Vite+Svelte セットアップ、SvelteKit+adapter-static セットアップ(フルプリレンダリングモードと SPA フォールバックモード)、trailing-slash/base-path の注意点、およびホストごとのデプロイに関する注意事項(GitHub Pages、Netlify、Cloudflare Pages、S3/CloudFront)。references/security.md— 完全なチェックリスト:XSS/{@html}/DOMPurify、静的ホスト向け CSP(メタタグ +_headersテンプレート)、シークレット/環境変数、安全な外部リンク、依存関係監査、Trusted Types、localStorage/sessionStorage の機密性、および出荷前チェックリスト。assets/— コピー可能なスターターファイル:vite.config.ts、svelte.config.js(adapter-static)、_headers(CSP テンプレート)、およびSecureHtml.svelte(DOMPurify ラッパーコンポーネント)。
必要なときにこれらを参照し、内容を記憶から再現しないでください。設定オプション名や CSP ディレクティブの詳細は、まさに古くなったり誤って記憶されたりする種類の詳細です。