mcpField
S
static-site

static-site

github.com/dfinityクラウド・DevOps3993 回インストール今日更新
リポジトリ

static-site について

Internet Computer 上で静的サイトを公開するデプロイレシピです。ビルド済みディレクトリを certified-assets canister にアップロードし、検証対応ゲートウェイが確認できる認証付き HTTP レスポンスを提供します。

主な機能

  1. 01単一ディレクトリのビルド成果物をデプロイ
  2. 02HTTP レスポンスの認証
  3. 03ビルドと同期前コマンドの設定
  4. 04_redirects によるルーティング設定
  5. 05_headers によるヘッダー設定

活用シーン

  1. 01Internet Computer でのフロントエンド公開
  2. 02SPA のディープリンク用リライト設定
  3. 03カスタムドメインでのサイト提供
  4. 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 のセクションのみです。値はビルド時にシェルで展開されるため、$(…) が使えます。

注意点

  1. 静的サイトのレシピで .ic-assets.json5 を使用する。 .ic-assets.json5 は従来の asset canisterの設定ファイルです。certified-assets canister はこれを読み込みません。また、プラグインはすべてのドットファイルとドットディレクトリをスキップするため(.well-known/ のみ例外。注意点12)、dir 内の .ic-assets.json5 はアップロードすらされません。ファイルは何も通知されずに存在しないものとして扱われるため、SPA フォールバック、ヘッダー、セキュリティポリシーは機能しません。代わりに _redirects と _headers を使って、この canister を設定してください(以下を参照)。

  2. SPA フォールバックルールが誤っている。 クライアントサイドルーティングでは、_redirects に /* /index.html 200 というリライトを指定します。200 ステータスによりリライトとして処理されます(リダイレクトが見えることなく、リクエストされた URL にシェルの内容を配信する)ため、ディープリンクは初回読み込み時も再読み込み時も機能します。301/302 リダイレクトは使用しないでください。また、enable_aliasing も使用しないでください。これは従来の asset canister の設定であり、ここでは効果がありません。

  3. SPA で相対アセットパスを使用する。 アセットには相対パス(assets/app.js)ではなく、絶対パス(/assets/app.js)を指定してください。/* のリライトでは、相対 URL はクライアントのルートを基準に解決されます。/dashboard/settings では、ブラウザーは /dashboard/settings/assets/app.js をリクエストしますが、/* は HTML シェルで応答します。その結果、スクリプトが読み込まれず、わかりにくい MIME タイプエラーが発生します。

  4. 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 を使っている可能性が非常に高いです。従来版のリファレンスを参照してください。)

  5. _headers/_redirects を誤った場所に置く。 これらのファイルは、dir のルート(例:dist/_redirects)に配置する必要があります。設定ファイルとして読み込まれ、アセットとして配信されることはありません。ビルド時に自動で dir にコピーされるよう、Vite では public/、または static/ フォルダーに配置してください。プロジェクトのルートに置いても dir にコピーされないファイルは、デプロイ時に存在しないものとして扱われます。

  6. _headers で予約済みヘッダーを設定する。 同期プラグインは、これらのヘッダーをデプロイ時に拒否し、説明的なエラーを表示します:Content-Length、Content-Encoding、ETag、Transfer-Encoding、Accept-Ranges、Content-Range、IC-Certificate、IC-CertificateExpression、Location。アセットのメディアタイプを設定するには、Content-Type: のみを指定する形式を使用してください(レスポンスヘッダーではなく、アセットのメタデータに設定されます)。リダイレクトには _redirects を使用してください。_headers の Location ヘッダーではリダイレクトされず、ステータスは 200 のままです。

  7. デフォルトのセキュリティヘッダーがあると思い込む。 従来の canister の security_policy: "standard" とは異なり、認証済みアセットの canister はデフォルトのヘッダーを追加しません。Cache-Control も、CSP も、X-Frame-Options もありません。これらが必要な場合は、_headers で宣言してください(以下の基本設定を参照)。この canister が独自に管理するのは、認証・配信に関するヘッダーと ic_env Cookie だけです。

  8. 大きなファイルを指す 404/410 ルールを設定する。 エラーページの転送先には、小さな単一チャンクのファイル(約1.9 MB未満)を指定する必要があります。大きなファイルは、4xx ステータスを付与できない認証済みの 206 レンジレスポンスとして配信されるため、プラグインはそのようなルールをデプロイ時に拒否し、該当するファイル名を表示します(大きなファイルへの 200 リライトは問題ありません)。

  9. 動的なリダイレクトキャプチャを期待する。 :splat も :placeholder もありません。キャプチャしたセグメントを転送することはできません(/old/:rest → /new/:rest)。認証可能なすべてのレスポンスは事前に列挙できる必要があるため、_redirects/_headers がサポートするのは、完全一致するパス、末尾が /* のサブツリー・ワイルドカード、固定の転送先のみです。

  10. 既存のプロジェクトを従来のアセット 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 が何も配信できない状態になります。移行のリファレンスを参照してください。

  11. レシピのバージョンを上げると自動的に再インストールされると思い込む。 認証済みアセットの独自のリリース間の移行は、上記の従来型からの切り替えよりも簡単です。リリースシリーズ全体で 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 を実行してください。)バージョン更新直後に同期が失敗するのは想定どおりです。バグではありません。

  12. .well-known/ は自動的にアップロードされるため、設定は不要です。 プラグインは、.well-known/ を除き、ドットファイルとドットディレクトリをスキップします。.well-known/ は通常どおり走査されます。そのため、dir/.well-known/ic-domains は追加設定なしで /.well-known/ic-domains として配信されます。(明示的な .ic-assets.json5 の無視解除ルールが必要だった従来の canister とは逆です。)

  13. アクセス保護の順序。 レシピの icp deploy は、キャニスターのインストールとアセットの同期を同時に行うため、通常のようにデプロイしてから enable_protection を実行すると、コンテンツが一時的に公開されます。新しく作成するプライベートアプリでは、実際のアセットを同期する前にアクセス保護を有効にしてください。login.html だけを含む dir をデプロイし、enable_protection を実行してから、サイト全体をデプロイします。これにより、アセットが公開されることはありません。ログインページは完全に自己完結している必要があります(CSS/JSをインライン化し、data: URIを使用してください)。このページだけがアクセス保護の対象外となり、参照先の外部サブリソースはすべてアクセス保護の対象になります。アクセス保護を参照してください。

  14. raw.icp.net URL の共有やリンク。 <canister-id>.raw.icp.net は <canister-id>.icp.net と同じサイトを提供しますが、ゲートウェイは証明書を確認せずにレスポンスを転送するため、訪問者が得られる保証は一般的なウェブホストと変わりません。従来のキャニスターにある allow_raw_access とは異なり、raw リクエストを拒否するキャニスター設定はありません。手掛かりとなるのは認証されていない Host ヘッダーだけであり、どのホスト名で検証が行われるかはプロトコルではなく、ゲートウェイのデプロイ方法によって決まります。https://<canister-id>.icp.net(または検証に対応したゲートウェイ上のカスタムドメイン)にリンクし、raw はデバッグ専用として扱ってください。アクセス保護が有効な場合、raw URL は非常に危険です。リダイレクトとログインページが検証されないため、検証を行わないゲートウェイが偽のログインフォームを表示し、トークンを盗み取る可能性があります。アクセス 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_env Cookie を 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_env Cookie — 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_env Cookie(バイト単位で同一のビルドでも、コントローラーは環境変数を使ってフロントエンドのバックエンドキャニスター 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_env Cookie/safeGetCanisterEnv() のパターンについては、icp-cli を読み込んでください。
  • カスタムドメインの DNS レコード、ACME チャレンジ、TLS の設定については、custom-domains を読み込んでください。
  • フロントエンドで ic_env からルートキーとキャニスター ID を読み込む方法については、internet-identity を読み込んでください。
  • 上流のユーザードキュメント全文:certified-assets のドキュメント。

コメント