static-site について
Internet Computer 上で静的サイトを公開するデプロイレシピです。ビルド済みディレクトリを certified-assets canister にアップロードし、検証対応ゲートウェイが確認できる認証付き HTTP レスポンスを提供します。
主な機能
- 01単一ディレクトリのビルド成果物をデプロイ
- 02HTTP レスポンスの認証
- 03ビルドと同期前コマンドの設定
- 04_redirects によるルーティング設定
- 05_headers によるヘッダー設定
活用シーン
- 01Internet Computer でのフロントエンド公開
- 02SPA のディープリンク用リライト設定
- 03カスタムドメインでのサイト提供
- 04デプロイ時のビルド成果物同期
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dfinity/ icskills --skill static-siteインストール後、Claude Code で/static-siteを入力して読み込まれたか確認します。
同梱ファイルreferences/legacy-asset-canister.mdreferences/migrating-from-asset-canister.md
詳細
Static Site(認証済みアセット)
これは何ですか
@dfinity/static-site レシピは、ビルド済みのフロントエンド、ドキュメント、または任意のファイルフォルダーといった静的サイトを、Internet Computer 上の認証済みアセット canisterにデプロイし、レスポンス認証を使って HTTP 経由で配信します。この canister はすべてのレスポンスを認証します(認証 v2 のみで、no_certification による回避策はなく、無効にする方法もありません)。そして、検証を行う HTTP ゲートウェイは、レスポンスをブラウザーに渡す前にその証明を確認します。訪問者には、転送中に改ざんされたものではなく、canister が確実にコミットしたコンテンツが届きます。証明が検証されるかどうかは、訪問者が経由するゲートウェイによって異なります。https://<canister-id>.icp.net は検証しますが、https://<canister-id>.raw.icp.net は検証しません(落とし穴 14)。
今後、IC 上でフロントエンドをホストする場合は、この方法を推奨します。 このレシピには、バージョンがそろった canister と同期プラグインのペアが含まれています。ビルドディレクトリを指定すると、icp deploy がアップロード、認証、配信を行います。
以前の
@dfinity/asset-canisterレシピ(.ic-assets.json5で設定する SDK アセット canister)は、既存のプロジェクトで引き続きサポートされますが、推奨される方法ではなくなりました。これは設定形式と API が異なる別の canisterです。詳しくはreferences/legacy-asset-canister.mdをご覧ください。既存のプロジェクトを移行するには、references/migrating-from-asset-canister.mdをご覧ください。
前提条件
icp-cli—npm install -g @icp-sdk/icp-cli。このレシピでは、ビルド済みの canister と同期プラグインのペアが固定されているため、通常のdir/build/presyncデプロイにはほかに何も必要ありません。ic-wasm—npm install -g @icp-sdk/ic-wasm。icp-cliには同梱されていない別個のバイナリです。このレシピでは、metadataフィールドを設定した場合に限りこれを呼び出します(生成されるビルドではcommand -v ic-wasmを確認し、見つからない場合は "ic-wasm not found" で失敗します)。ほかの公式レシピでは必須となるため、あらかじめ両方をインストールしておくのが安全です。詳しくはicp-cliスキルをご覧ください。- フロントエンドのビルドツールチェーン(例:Vite/React アプリの場合は Node.js >= 22)。
Canister ID と URL
静的サイトの canister はプロジェクトごとに作成されるため、グローバルな canister ID はありません。デプロイ後、canister ID は .icp/<cache|data>/mappings/<environment>.ids.json に保存されます。管理対象ネットワーク(ローカルレプリカ)はキャッシュで、.icp/cache/mappings/local.ids.json に保存されます。接続済みネットワーク(メインネット ic)はデータで、.icp/data/mappings/ic.ids.json に保存されます。
| 環境 | ブラウザー URL |
|---|---|
| ローカル | http://<canister-name>.<environment>.localhost:8000 — 中間のラベルは環境名で、デフォルトは local です。そのため、http://frontend.local.localhost:8000 となります(これは icp deploy が表示する URL です。http://<canister-id>.localhost:8000 も機能しますが、環境ラベルのない <canister-name>.localhost は機能しません) |
| メインネット | https://<canister-id>.icp.net |
| カスタムドメイン | https://yourdomain.com(DNS 設定が必要です) |
icp.yaml の設定
canisters:
- name: frontend
recipe:
type: "@dfinity/[email protected]"
configuration:
build:
- npm ci
- npm run build
dir: dist
最新バージョンは静的サイトのリリースで確認し、type フィールドに固定してください。このレシピでは、対応する canister とプラグインのペアが固定されるため、canister のバージョンを別途選ぶ必要はありません。レシピのバージョンが canister のバージョン です。
レシピで設定するフィールドは4つです。
| フィールド | 必須 | 説明 |
|---|---|---|
dir | はい | 配信するビルド済みファイルが格納されたディレクトリを1つ指定します。canister は URL 空間全体を所有するため、複数のディレクトリではなく1つのディレクトリを指定します。Vite → dist、Next.js のエクスポート → out。 |
build | いいえ | dir を生成するために、canister が存在する前に実行するシェルコマンドです(例:npm run build)。この時点では canister ID を使用できません。 |
presync | いいえ | 同期時、canister の作成後に実行するシェルコマンドです。デプロイ済み canister ID は環境変数としてエクスポートされます。canister ID をフロントエンドのビルドに埋め込む場合に使用します(以下を参照)。 |
metadata | いいえ | ic-wasm を使って canister の wasm に埋め込む name/value/visibility のエントリーです。visibility は省略可能で、public または private を指定します。省略した場合は private(ic-wasm のデフォルト)となり、icp canister metadata で誰でも読み取れるのは public のセクションのみです。値はビルド時にシェルで展開されるため、$(…) が使えます。 |
注意点
-
静的サイトのレシピで
.ic-assets.json5を使用する。.ic-assets.json5は従来の asset canisterの設定ファイルです。certified-assets canister はこれを読み込みません。また、プラグインはすべてのドットファイルとドットディレクトリをスキップするため(.well-known/のみ例外。注意点12)、dir内の.ic-assets.json5はアップロードすらされません。ファイルは何も通知されずに存在しないものとして扱われるため、SPA フォールバック、ヘッダー、セキュリティポリシーは機能しません。代わりに_redirectsと_headersを使って、この canister を設定してください(以下を参照)。 -
SPA フォールバックルールが誤っている。 クライアントサイドルーティングでは、
_redirectsに/* /index.html 200というリライトを指定します。200ステータスによりリライトとして処理されます(リダイレクトが見えることなく、リクエストされた URL にシェルの内容を配信する)ため、ディープリンクは初回読み込み時も再読み込み時も機能します。301/302リダイレクトは使用しないでください。また、enable_aliasingも使用しないでください。これは従来の asset canister の設定であり、ここでは効果がありません。 -
SPA で相対アセットパスを使用する。 アセットには相対パス(
assets/app.js)ではなく、絶対パス(/assets/app.js)を指定してください。/*のリライトでは、相対 URL はクライアントのルートを基準に解決されます。/dashboard/settingsでは、ブラウザーは/dashboard/settings/assets/app.jsをリクエストしますが、/*は HTML シェルで応答します。その結果、スクリプトが読み込まれず、わかりにくい MIME タイプエラーが発生します。 -
AssetManager/@icp-sdk/canistersが使えると思い込む。 certified-assets canister のアップロード API はupload_chunks+execute_operationsであり、SDK の asset canister のstore/create_batch/commit_batchではありません。@icp-sdk/canisters/assetsのAssetManagerは従来の canister を対象としており、static-site では動作しません。アップロードはicp deploy時にレシピの同期プラグインを通じて行われます。この canister にそのまま置き換えて使える JavaScript のAssetManager相当機能はありません。(プログラムからのアップロードが必要な場合は、従来の canister を使っている可能性が非常に高いです。従来版のリファレンスを参照してください。) -
_headers/_redirectsを誤った場所に置く。 これらのファイルは、dirのルート(例:dist/_redirects)に配置する必要があります。設定ファイルとして読み込まれ、アセットとして配信されることはありません。ビルド時に自動でdirにコピーされるよう、Vite ではpublic/、またはstatic/フォルダーに配置してください。プロジェクトのルートに置いてもdirにコピーされないファイルは、デプロイ時に存在しないものとして扱われます。 -
_headersで予約済みヘッダーを設定する。 同期プラグインは、これらのヘッダーをデプロイ時に拒否し、説明的なエラーを表示します:Content-Length、Content-Encoding、ETag、Transfer-Encoding、Accept-Ranges、Content-Range、IC-Certificate、IC-CertificateExpression、Location。アセットのメディアタイプを設定するには、Content-Type:のみを指定する形式を使用してください(レスポンスヘッダーではなく、アセットのメタデータに設定されます)。リダイレクトには_redirectsを使用してください。_headersのLocationヘッダーではリダイレクトされず、ステータスは200のままです。 -
デフォルトのセキュリティヘッダーがあると思い込む。 従来の canister の
security_policy: "standard"とは異なり、認証済みアセットの canister はデフォルトのヘッダーを追加しません。Cache-Controlも、CSP も、X-Frame-Optionsもありません。これらが必要な場合は、_headersで宣言してください(以下の基本設定を参照)。この canister が独自に管理するのは、認証・配信に関するヘッダーとic_envCookie だけです。 -
大きなファイルを指す
404/410ルールを設定する。 エラーページの転送先には、小さな単一チャンクのファイル(約1.9 MB未満)を指定する必要があります。大きなファイルは、4xx ステータスを付与できない認証済みの206レンジレスポンスとして配信されるため、プラグインはそのようなルールをデプロイ時に拒否し、該当するファイル名を表示します(大きなファイルへの200リライトは問題ありません)。 -
動的なリダイレクトキャプチャを期待する。
:splatも:placeholderもありません。キャプチャしたセグメントを転送することはできません(/old/:rest → /new/:rest)。認証可能なすべてのレスポンスは事前に列挙できる必要があるため、_redirects/_headersがサポートするのは、完全一致するパス、末尾が/*のサブツリー・ワイルドカード、固定の転送先のみです。 -
既存のプロジェクトを従来のアセット canister から static-site に切り替える。
recipe:を@dfinity/static-siteに変更して通常のicp deployを実行すると、何もインストールされる前に失敗します。これは互いに無関係な Candid インターフェースを持つ、別々の canister だからです。そのため、icp-cli のインストール前チェックが中断され、Candid interface compatibility check failed: '<canister>' … You are making a BREAKING changeと表示されます。代わりにicp deploy --mode reinstallを実行してください。これにより wasm が認証済みアセットの canister に置き換えられ、以前の安定メモリは破棄されます。従来のアセット、権限、.ic-assets.json5由来の設定はすべて失われ、canister は空の状態になります。その後、同期プラグインがdir全体を最初からアップロードします。static-site を新しい canister としてデプロイすれば、この問題は発生しません。--yesでチェックを無効にしないでください。そうすると、認証済みアセットの canister が読み取れない安定メモリに対してインプレースアップグレードが実行され、稼働中の canister が何も配信できない状態になります。移行のリファレンスを参照してください。 -
レシピのバージョンを上げると自動的に再インストールされると思い込む。 認証済みアセットの独自のリリース間の移行は、上記の従来型からの切り替えよりも簡単です。リリースシリーズ全体で Candid インターフェースが安定しているため、デプロイが妨げられることはありません。ただし、破壊的変更を含むバージョンへの更新では、自分で再インストールを実行する必要があります。canister とプラグインはバージョンが固定されているため、レシピのバージョンを上げた後、
icp deployはインプレースでアップグレードしますが、その後、同期プラグインはassets canister version mismatch: canister is X, this plugin is Yとエラーを報告して停止します。修正方法として、状態を保持するパッチバージョンへの更新ではicp canister install --mode upgrade、状態が消去され、次の同期ですべてのアセットとリダイレクトルールが再アップロードされる破壊的変更を含む更新(1.0未満ではマイナーバージョン、1.0以降ではメジャーバージョン)ではicp canister install --mode reinstallを実行してください。(v0.3.x→v0.4.0は破壊的変更です。再インストールしてからicp deployを実行してください。)バージョン更新直後に同期が失敗するのは想定どおりです。バグではありません。 -
.well-known/は自動的にアップロードされるため、設定は不要です。 プラグインは、.well-known/を除き、ドットファイルとドットディレクトリをスキップします。.well-known/は通常どおり走査されます。そのため、dir/.well-known/ic-domainsは追加設定なしで/.well-known/ic-domainsとして配信されます。(明示的な.ic-assets.json5の無視解除ルールが必要だった従来の canister とは逆です。) -
アクセス保護の順序。 レシピの
icp deployは、キャニスターのインストールとアセットの同期を同時に行うため、通常のようにデプロイしてからenable_protectionを実行すると、コンテンツが一時的に公開されます。新しく作成するプライベートアプリでは、実際のアセットを同期する前にアクセス保護を有効にしてください。login.htmlだけを含むdirをデプロイし、enable_protectionを実行してから、サイト全体をデプロイします。これにより、アセットが公開されることはありません。ログインページは完全に自己完結している必要があります(CSS/JSをインライン化し、data:URIを使用してください)。このページだけがアクセス保護の対象外となり、参照先の外部サブリソースはすべてアクセス保護の対象になります。アクセス保護を参照してください。 -
raw.icp.netURL の共有やリンク。<canister-id>.raw.icp.netは<canister-id>.icp.netと同じサイトを提供しますが、ゲートウェイは証明書を確認せずにレスポンスを転送するため、訪問者が得られる保証は一般的なウェブホストと変わりません。従来のキャニスターにあるallow_raw_accessとは異なり、rawリクエストを拒否するキャニスター設定はありません。手掛かりとなるのは認証されていないHostヘッダーだけであり、どのホスト名で検証が行われるかはプロトコルではなく、ゲートウェイのデプロイ方法によって決まります。https://<canister-id>.icp.net(または検証に対応したゲートウェイ上のカスタムドメイン)にリンクし、rawはデバッグ専用として扱ってください。アクセス保護が有効な場合、rawURL は非常に危険です。リダイレクトとログインページが検証されないため、検証を行わないゲートウェイが偽のログインフォームを表示し、トークンを盗み取る可能性があります。アクセス Cookie はホスト専用のため、検証済みホストでのセッションはrawに引き継がれません。
SPA のルーティングとリダイレクト:_redirects
dir のルートに _redirects ファイルを追加します。構文は Netlify の _redirects に準じます。1 行につき 1 つのルールを記述し、空白区切りの 3 つのフィールド(from、to、status)を指定します。空行と # コメントは無視されます。
# Single-page-app fallback: serve the shell for any unmatched path (a rewrite, status 200)
/* /index.html 200
# Permanent redirect to a new internal path
/old-blog /blog 301
# Redirect to an external site
/discord https://discord.gg/example 302
# Rewrite: serve /content/article.html at a pretty URL, no visible redirect
/article /content/article.html 200
# Subtree redirect: everything under /docs/v1/ moves to /docs/v2/
/docs/v1/* /docs/v2/ 301
# Custom error pages (target must be a small single-chunk file)
/secret /403.html 404
/retired-feature /sunset.html 410
ステータスコード:301/302/307/308 はリダイレクト、200 はリライト、404/410 はエラーページを表示します。from は常に絶対パスで、使用できるワイルドカードは末尾の /* のみです。優先順位は、実ファイル > 自動のクリーン URLルール > _redirects のルール(ファイル内の順序) > 404 フォールバックで、最初に一致したルールが適用されます。実ファイルは常に優先されるため、/* の SPA ルールに一致するのは、ほかのルールに該当しないパスだけです。
SPA に関する注意 — 存在しないアセットも HTML を返します。 /* /index.html 200 を設定すると、誤記された /assets/app-old.js も /* に一致し、シェルが text/html として返されます。ビルド出力フォルダー以下で実際の 404 を返すには、キャッチオールより上に、より限定的なルールを追加します(ルールはファイル内の順序で照合されます)。
/assets/* /404.html 404
/* /index.html 200
これには dir 内に 404.html が必要です。/* を宣言すると、組み込みのデフォルト 404 は追加されません。
カスタムヘッダー:_headers
dir のルートに _headers ファイルを追加します。構文は Netlify の _headers に従います。パスパターンを単独の行に記述し、その後にインデントした Name: value の行を続けます。空行または # でブロックが終了します。パターンは絶対パスで、任意の単一の * ワイルドカードを使用できます(/ にも一致します。** や ? はありません)。
実用的な基本設定 — シニアセキュリティエンジニアのように考え、アプリの CSP を強化してください:
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'
# Fingerprinted build assets never change — cache them hard
/assets/*
Cache-Control: public, max-age=31536000, immutable
# HTML should revalidate so deploys are picked up
/*.html
Cache-Control: public, max-age=0, must-revalidate
重要なルール:
- パターンは訪問者の URL ではなく、ファイル(アセットキー)に一致します。
/ではなく/index.htmlと記述してください。SPA の場合、/index.html(または/*.html)に設定したCache-Controlは、/*にリライトされるすべてのクライアントルートに適用されます。/dashboard/*のようなルートを指定したブロックは、どのファイルにも一致せず、何も行いません。 - 一致するブロックはすべて適用されます — 複数のブロックに一致するファイルには、それぞれのブロックのヘッダーがすべて適用されます(同じ名前の値はカンマ区切りで結合されます。
Set-Cookieは個別のままです)。 Content-Typeは特殊です —Content-Type: <type>の形式は、一致するファイルに保存されたメディアタイプを上書きします(/llms.txtのような拡張子のないファイルに使用します)。値は単一で、最初に一致したものが優先され、通常のヘッダーとしては出力されません。Set-Cookieは使用できますが、ic_envという名前は使用しないでください。 カナスターは HTML レスポンスに、同じ名前と値を持つ独自のic_envCookie を 2 つ のSet-Cookieヘッダーとして追加します。一方はSameSite=Lax、もう一方はSameSite=None; Partitionedです(クロスサイトの iframe 内でも保持されます)。ic_envヘッダーが 2 つ表示されるのは想定どおりであり、ヘッダー重複の不具合ではありません。- 予約済みヘッダーはデプロイ時に拒否されます — Pitfall 6 を参照してください。
クリーン URL と 404 ページ
.html ファイルへのリンクは不要です。カナスターは各 HTML ファイルを拡張子のない正規 URL にマッピングし、その他の形式からその URL へ 307 リダイレクトします:
| ファイル | 正規 URL | こちらも処理されます(→ 307) |
|---|---|---|
dist/index.html | / | /index |
dist/about.html | /about | /about/、/about/index |
dist/blog/index.html | /blog/ | /blog、/blog/index |
基になる /about.html に直接リクエストすると、現在は 200 で配信されます(ファイルは常にルーティングルールより優先されます)。カスタム 404 ページを設定するには、dir のルートに 404.html を配置してください。そうでない場合は、認証済みのデフォルトページが配信されます。SPA の /* ルールはパス空間全体を引き受けるため、デフォルトの 404 ページはシェルに置き換わります。
カスタムドメイン
独自ドメインから配信するには、dir に .well-known/ic-domains ファイルを追加し、1 行につき 1 つのドメインを記述します:
example.com
www.example.com
.well-known/ は自動的にアップロードされるため(Pitfall 12)、ファイルは IC 境界ノードが参照する場所で配信されます。ドメイン自体の登録(DNS レコード、ACME チャレンジ、TLS のプロビジョニング)は、別途 IC プラットフォームで行う手順です — custom-domains スキルを参照してください。
アクセス保護(非公開アプリ)
開発中またはプレビュー中のアプリの前にログインゲートを設置します。認証されていない訪問者には、認証済みの 307 → <login_page>(HTML)または 401(その他のアセット)が返されます。コントローラー専用の canister メソッドで設定します。
# 1. Add a self-contained /login.html to your dir and deploy.
icp deploy
# 2. Turn the gate on, naming your login page.
icp canister call frontend enable_protection '("/login.html")'
# 3. Mint a credential (here: a chosen passphrase valid ~1 year). Pass value = null for a random token.
# issue_token takes a record (IssueTokenArgs), not positional args.
icp canister call frontend issue_token '(record { label = "owner"; ttl_secs = 31536000 : nat32; value = opt "my-passphrase" })'
実行順序と公開される時間帯。
static-siteレシピのicp deployは、canister のインストールとアセットの同期を一度に行うため、上記の手順では最初のデプロイからenable_protectionまでの短い間、コンテンツが公開されます。既存のアプリやプレビュー中のアプリを保護する場合は問題ありません。新しい非公開アプリを一切公開せずに開始するには、本番用アセットを同期する前に保護を有効にします。login.htmlのみを含むdirを指定して最初のicp deployを実行し、次にenable_protection '("/login.html")'を実行してから、サイト全体を含む状態でicp deployをもう一度実行します。ログインページはゲートの適用対象外なので、非公開コンテンツが認証なしで配信されることはありません(Pitfall 13)。 (完全に空の canister で有効にする方法も機能します。この場合、canister はEnabledLoginPageMissingを報告し、login.htmlが同期されると自動的にEnabledに復帰します。)
| メソッド | 効果 |
|---|---|
issue_token '(record { label = "<label>"; ttl_secs = <secs> : nat32; value = opt "<value>" })' | トークンを発行し、その値を返します。value = null の場合、高エントロピーのランダムなトークンになります。 |
revoke_token '("<label>")' | そのラベルのトークンをすべて即時に削除します。 |
list_tokens '()' | 有効なトークン { label; expires_at } を返します(コントローラー専用)。 |
check_protection_status '()' | Disabled、Enabled、または EnabledLoginPageMissing を返します。 |
disable_protection '()' | ゲートを無効にし、すべてのトークンを削除します。 |
引数は常に明示的に渡してください。引数を取らないメソッドには '()' を渡します。引数を指定せずに呼び出すと、空の引数が送信されるのではなく、icp canister call は対話型プロンプトを開きます。
これはアクセス制御であり、機密性の保証ではありません。ノードオペレーターはアセットのバイト列とトークンストアを読み取ることができ、レート制限もなく、誠実なレプリカと誠実なゲートウェイを前提としています。ゲートはすべてのホスト名で canister 側で実行されますが、リダイレクトとログインページが検証されるのは検証ゲートウェイ経由のみです。そのため、保護されたアプリの raw URL を人に案内しないでください(Pitfall 14)。共有リンクには高エントロピーのランダムなトークンを使用し、新しい非公開アプリでは最初の同期前に有効にしてください(Pitfall 13)。詳しくは認証済みアセットのアクセス保護に関するドキュメントを参照してください。
アップロードを許可するユーザーの認可
アップロード(同期)は、canister のコントローラーと、別に設定された認可済み同期用 principalによって実行されます。CI/デプロイ用 principal に canister の完全な制御権を与えずに同期を許可するには、その principal を authorize で認可してください。アップロード権限をはるかに超えるアップグレード/設定変更/削除の権限を与える --add-controller は使用しないでください。
# Allow a principal to sync assets (upload), without making it a controller
icp canister call frontend authorize '(principal "<principal-id>")'
# List authorized syncers
icp canister call frontend list_authorized '()'
# Revoke
icp canister call frontend deauthorize '(principal "<principal-id>")'
セキュリティ警告:
icp canister settings update frontend --add-controller <principal-id>は、アップロード権限だけでなく、canister の完全な制御権(Wasm のアップグレード、設定変更、削除、cycles の引き出し)を付与します。デプロイパイプラインにはauthorizeを使用してください。
Canister ID を利用したビルド(presync と build)
build は canister の作成前に実行されるため、canister ID を取得できません。クライアント側アプリで、呼び出し先 canister の ID をビルド時に埋め込む必要がある場合は、presync でビルドしてください。これは同期時に実行され、その時点では ID が存在するため、ID を環境変数として提供できます。
canisters:
- name: frontend
recipe:
type: "@dfinity/[email protected]"
configuration:
dir: dist
presync:
- npm ci
# $ICP_CLI_CID_BACKEND is the `backend` canister's principal.
- VITE_CANISTER_ID_BACKEND=$ICP_CLI_CID_BACKEND npm run build
presync で利用可能な変数:ICP_CLI_CID(この canister)、ICP_CLI_CID_<NAME>(プロジェクト内の各 canister。名前は大文字に変換され、英数字以外は _ に置き換えられます。例:backend → ICP_CLI_CID_BACKEND)、ICP_CLI_NETWORK、ICP_CLI_ENVIRONMENT。
または、ブラウザーで 実行時に、canister がすべての HTML レスポンスに設定する ic_env cookie から canister ID を読み取ることもできます(環境による分岐なしで、ローカルと mainnet の両方で動作します)。safeGetCanisterEnv() パターンについては、internet-identity および icp-cli のスキルを参照してください。fetchRootKey() より cookie を優先してください。
自動で適用される機能
設定は不要です。デフォルトで有効になっています。
- レスポンスの認証 — すべてのレスポンスが認証されます。検証ゲートウェイ(
rawではありません)が証明をチェックします。 - クリーンな URL —
307による正規化(上記)。 - 圧縮 — 圧縮可能なアセットは、元のファイルとともに gzip および Brotli 形式で保存され、
Accept-Encodingによってリクエストごとに適切な形式が選択されます。圧縮可能なものは、あらゆるtext/*、あらゆる+jsonまたは+xmlサフィックス(image/svg+xml、application/xhtml+xmlなど)、application/javascript、application/json、application/xml、application/wasm、およびwoff/woff2(すでに圧縮済み)を除くあらゆるfont/*です。エンコード後のサイズが元のファイルより実際に小さい場合にのみ、そのエンコード形式が保持されます。 - ETag/
304 Not Modified— コンテンツハッシュの ETag を使用します。変更されていないファイルは再ダウンロードされません。 - デフォルトの認証済み
404— 独自の/404.htmlに置き換えられます。 ic_envCookie — HTML レスポンスに付与されます(LaxとNone; Partitionedの 2 つのSet-Cookieヘッダー)。PUBLIC_*環境変数(キャニスター ID)と、フロントエンド用のルートキーが含まれます。
デプロイと検証
# Start the local network
icp network start -d
# Build + deploy everything (or just the frontend)
icp deploy
icp deploy frontend
# Mainnet (needs cycles)
icp deploy -e ic frontend
icp deploy を再実行すると再同期されます。プラグインがディレクトリとキャニスターの内容を比較し、変更されたものだけをアップロードします。
# Canister is running
icp canister status frontend # Status: Running, non-zero memory
# Get the canister ID (there is no `icp canister id`; use status --id-only)
icp canister status frontend --id-only
# Fetch the index page (certified)
icp canister call frontend http_request '(record {
url = "/"; method = "GET"; body = vec {}; headers = vec {};
certificate_version = opt 2;
})' # → status_code = 200
# SPA fallback returns the shell, not 404
icp canister call frontend http_request '(record {
url = "/dashboard/settings"; method = "GET"; body = vec {}; headers = vec {};
certificate_version = opt 2;
})' # → 200 (index.html), NOT 404
# Open in a browser (this is the URL `icp deploy` prints)
# Local: http://frontend.local.localhost:8000
# Mainnet: https://<frontend-canister-id>.icp.net
デプロイしたコンテンツの検証(状態ハッシュ)
認証によって、レスポンスがキャニスターのコミット内容と一致することが証明されます。状態ハッシュによって、そのコミット内容が公開ソースから再現したビルドと一致することが証明されます。icp deploy が出力するハッシュ(canister reports state hash <hex>)はキャニスター自体から取得したものです。これはデプロイの自己整合性チェックであり、第三者による検証ではありません。キャニスターを検証するには、次のようにします。
# 1. Reproduce the build from source at the deployed version → ./dist (incl. _headers/_redirects)
# 2. Ask the canister its release, then build the verifier from THAT tag.
# Not published as a binary or on crates.io. --locked is required: Cargo.lock pins the
# compressor builds whose output bytes the hash covers; without it the hash differs.
icp canister call <canister-id> version '()' -n ic --query
# (record { major = 0 : nat32; minor = 4 : nat32; patch = 0 : nat32 })
cargo install --git https://github.com/dfinity/certified-assets --tag v0.4.0 --locked state-hash-cli
# 3. Compute the hash locally (prints 64 hex chars)
state-hash ./dist
# 4. Read the canister's hash (an update call, so consensus-backed); the hash is the last 32 bytes
icp canister call <canister-id> state_hash '()' -n ic -o hex | tail -c 65
-n <network>を指定し、プリンシパルでキャニスターを指定します。-e <environment>はローカルプロジェクトからキャニスターの名前を解決しますが、第三者の検証者はそのプロジェクトを持っていません。'()'を明示的に渡してください。- 32 バイトのゼロはハッシュではありません。キャニスターの同期が一度も完了していないか、同期が進行中です(同期が始まるとキャッシュされたハッシュは削除されます)。デプロイの完了後に再度読み込んでください。
- ハッシュが一致しない場合、コンテンツ、ヘッダー、またはリダイレクトがソースと異なります。または、標準外の圧縮器を使ってキャニスターが準備されています(クレートを組み込むプラットフォームではこのような場合があります。このツールは
icp deployと同じ処理のみを再現します)。 - ハッシュの対象外:
ic_envCookie(バイト単位で同一のビルドでも、コントローラーは環境変数を使ってフロントエンドのバックエンドキャニスター ID を変更できます)とアクセス保護(一致するハッシュが示すのは、キャニスターにビルドが格納されていることだけであり、訪問者がアクセスできることではありません)。
レガシーアセットキャニスターと移行
- 既存の
@dfinity/asset-canisterプロジェクトの保守(.ic-assets.json5、AssetManagerによるアップロード、grant_permissionロール、allow_raw_access):references/legacy-asset-canister.mdを参照してください。 - 既存の asset-canister プロジェクトを certified-assets/static-site に移行:設定のマッピングと再インストールモードに関する注意点については、
references/migrating-from-asset-canister.mdを参照してください。
追加のリファレンス
- レシピシステム、
icp.yamlの構造、キャニスター ID の注入、ic_envCookie/safeGetCanisterEnv()のパターンについては、icp-cliを読み込んでください。 - カスタムドメインの DNS レコード、ACME チャレンジ、TLS の設定については、
custom-domainsを読み込んでください。 - フロントエンドで
ic_envからルートキーとキャニスター ID を読み込む方法については、internet-identityを読み込んでください。 - 上流のユーザードキュメント全文:certified-assets のドキュメント。