静的アセットでヘルプセンターを提供する

CloudflareBeginner
オンラインで実践に進む

はじめに

ヘルプセンターには、高速な公開ページ、JSON 形式のヘルスエンドポイント、スタッフ専用ドキュメントが必要です。静的ファイルが、リクエストを処理するはずのコードよりも誤って優先されることがあります。この実験では、まずローカルでその動作を確認し、Worker を優先するルーティングを設定します。その後、公開サイトの利便性を保ちながら、合成スタッフ用フィクスチャを保護する明示的なポリシーをデプロイします。

この独立した実験は、Node.js 22.22.0、プロジェクトローカルの Wrangler 4.131.1、提供済みの HTML/CSS/JavaScript フィクスチャが用意された /home/labex/project/help-center から開始します。以前のステップで学んだ、自分の Cloudflare 学習用アカウント、認証、デプロイ、シークレットファイルの扱い方を使用してください。以前の VM、クラウドリソース、購入済みドメイン、データベース、有料アップグレードは必要ありません。リクエストは通常のアカウント利用量としてカウントされます。

すべてのコンテンツと認証情報は合成データです。ターミナルは 1 つ開いたままにしてください。安全でない初期構成はローカルにのみ保持し、修正済みの Worker だけをデプロイします。VM を終了する前に、デプロイメントを削除し、ローカルのテスト用認証情報を削除して、ログアウトしてください。

ローカルでアセット優先のルーティングを確認する

このステップでは、用意されたヘルプセンターの外枠を調べ、デフォルトでは一致するファイルが Worker より優先されることを確認します。フィクスチャには、意図的に競合する /api/health ファイルと、偽のスタッフ用ハンドブックが含まれています。すべて合成データであり、この最初の構成はローカルでのみ使用します。

cd /home/labex/project/help-center
node --version
npx wrangler --version
ls -R public

Node は v22.22.0、Wrangler は 4.131.1 と表示されるはずです。セットアップではプロジェクトローカルのツールがインストールされています。別の環境で再現する場合は、プロジェクトの lockfile を使って npm ci を実行してください。public ディレクトリには、HTML、CSS、ブラウザ用 JavaScript、そして 2 つのルーティング用フィクスチャがあります。認証情報や実際の内部文書をここに置かないでください。

WORKER_NAME="labex-help-$(openssl rand -hex 6)"
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false,
  "assets": {
    "directory": "./public",
    "binding": "ASSETS",
    "html_handling": "none",
    "not_found_handling": "none",
    "run_worker_first": false
  }
}
CONFIG

directory はアップロードするファイルを指定し、binding はそれらのファイルをハンドラーから env.ASSETS として利用できるようにします。html_handling: none により、明示的なファイルパスをそのまま使用します。not_found_handling: none により、自動的な SPA フォールバックを無効にします。自動 HTML 処理を無効にしているため、ハンドラーは //index.html に明示的に対応付けます。ヘルスチェックには JSON を返し、それ以外のパスはアセットストアに委譲する構成です。

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    if (new URL(request.url).pathname === '/api/health') {
      return Response.json({status: 'ok', service: 'help-center'});
    }
    const assetUrl = new URL(request.url);
    if (assetUrl.pathname === '/') assetUrl.pathname = '/index.html';
    return env.ASSETS.fetch(new Request(assetUrl, request));
  }
};
JS
npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

続行する前に、ログに準備完了を示すメッセージが表示されるまで待ちます。必要に応じて cat を繰り返してください。

curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/styles.css
curl -i http://127.0.0.1:8080/api/health
curl -i http://127.0.0.1:8080/staff/handbook.html

ホームページと CSS は 200 を返します。ヘルスチェックは、ハンドラーが返す JSON ではなく、静的なテキスト STATIC_HEALTH_PLACEHOLDER を返します。これは、一致するアセットが優先されるためです。合成されたハンドブックも直接読み取れます。ここで確認しているのはルーティングの優先順位であり、安全なデプロイメントではありません。この初期構成はデプロイしないでください。変更する前に検証を行います。

実験環境で Web 8080 プレビューが利用できる場合は、ここで開いてください。ヘルプセンターの外枠は表示されますが、ブラウザが JSON を想定していたため、ステータス行には API status unavailable と表示されます。ルーティング確認の正式な判断には CLI のレスポンスを使い、プレビューは表示状態を確認するために使ってください。

次の例は、開始時点の問題を示しています。ページとスタイルシートは読み込まれますが、API status unavailable は、ブラウザが期待したヘルス JSON を受け取っていないことを意味します。ページの外枠が表示されるだけでは、API ルーティングが機能しているとは判断できません。

ルーティング修正前のヘルプセンター。API status unavailable と表示されています

アセットより先に Worker を実行し、スタッフコンテンツを保護する

このステップでは、静的アセットとの一致を確認する前にハンドラーを実行します。jobs に表示された実際の開発ジョブを停止してください。以下の例ではジョブ 1 を想定しています。

jobs
kill %1
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false,
  "assets": {
    "directory": "./public",
    "binding": "ASSETS",
    "html_handling": "none",
    "not_found_handling": "none",
    "run_worker_first": true
  }
}
CONFIG

run_worker_first: true にすると、通常なら直接一致するファイルを含め、すべてのリクエストがハンドラーを通ります。特定のルートパターンを選ぶ方法もありますが、この小さなアプリでは 1 つの明示的なルーティングポリシーを使用します。詳しくは Static Assets configuration を参照してください。

以前のステップで学んだシークレットファイルの手順を使って、使い捨てのスタッフ認証情報を生成します。umask は新しく作成するファイルの権限を制限します。これは実験専用の Bearer トークンであり、アカウント API トークンではありません。公開ファイル、ブラウザ用 JavaScript、URL、ログには決して含めないでください。

umask 077
STAFF_TOKEN=$(openssl rand -hex 24)
printf 'STAFF_TOKEN=%s\n' "$STAFF_TOKEN" > .dev.vars
cat .gitignore

.dev.vars*.env* が無視対象になっていることを確認します。次の完全なポリシーでハンドラーを置き換えます。このコードはパスを一度だけデコードし、ヘルスチェックには JSON を返し、一覧にある公開ファイルだけを許可します。スタッフ認証情報を確認してから対象アセットを取得し、それ以外のパスは拒否します。ASSETS に送るリクエストには、クライアントの Authorization ヘッダーを含めません。保護されたレスポンスには private, no-store を設定します。

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    let path;
    try { path = decodeURIComponent(url.pathname); }
    catch { return Response.json({error: 'not_found'}, {status: 404}); }
    if (path === '/api/health') {
      if (request.method !== 'GET') {
        return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET'}});
      }
      return Response.json({status: 'ok', service: 'help-center'});
    }
    const publicPaths = ['/', '/index.html', '/styles.css', '/app.js'];
    if (path === '/staff/handbook.html') {
      if (!env.STAFF_TOKEN) return Response.json({error: 'staff_unconfigured'}, {status: 503});
      if (request.headers.get('Authorization') !== `Bearer ${env.STAFF_TOKEN}`) {
        return Response.json({error: 'unauthorized'}, {status: 401});
      }
    } else if (!publicPaths.includes(path)) {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    if (!['GET', 'HEAD'].includes(request.method)) {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET, HEAD'}});
    }
    url.pathname = path === '/' ? '/index.html' : path;
    // Only known paths reach the asset store, after any required authorization.
    const response = await env.ASSETS.fetch(new Request(url, {method: request.method}));
    if (path === '/staff/handbook.html') {
      const headers = new Headers(response.headers);
      headers.set('Cache-Control', 'private, no-store');
      return new Response(response.body, {status: response.status, headers});
    }
    return response;
  }
};
JS
npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

準備完了を確認したら、公開、保護対象、不明なパスのレスポンスを比較します。

curl -i http://127.0.0.1:8080/api/health
curl -i http://127.0.0.1:8080/staff/handbook.html
curl -i http://127.0.0.1:8080/staff/handbook.html -H "Authorization: Bearer wrong-token"
curl -i http://127.0.0.1:8080/staff/handbook.html -H "Authorization: Bearer $STAFF_TOKEN"
curl -i --path-as-is http://127.0.0.1:8080/%73taff/handbook.html
curl -i http://127.0.0.1:8080/missing-page -H "Sec-Fetch-Mode: navigate"

ヘルスチェックは、競合するファイルが残っているにもかかわらず、200 と {"status":"ok","service":"help-center"} を返すようになります。認証情報がない場合や間違っている場合は 401 の JSON が返り、正しいトークンを指定すると合成されたハンドブックの HTML が返ります。エンコードされたスタッフパスも 401 を返し、不明なナビゲーションパスは 404 の JSON を返します。フィクスチャを削除するとルーティングの問題が見えなくなるため、残しておいてください。

任意の Web 8080 プレビューを更新し、ステータスが API status: ok になっていることを確認します。認証情報なしでハンドブックのエンドポイントにアクセスすると 401 の JSON が返ります。埋め込みブラウザによっては、そのレスポンスへの移動をブロックして、前のページを表示したままにすることがあります。その場合は、上記の curl の結果で確認してください。このブラウザの動作は、アクセス成功の証拠ではありません。認証済みアクセスには合成ヘッダーを付けた curl を使い、アドレスバーにシークレットを貼り付けないでください。サーバーを起動した状態で検証します。検証では HEAD、エンコードされたパス、別表記のパス、公開アセットの種類も確認します。

先ほどのプレビューとステータス行を比較してください。API status: ok になっていれば、ページがヘルスレスポンスを読み取れていることが分かります。この表示確認で対象になるのは公開ヘルスルートです。保護されたハンドブックについては、上記の curl レスポンスを使って判断してください。

ルーティング修正後のヘルプセンター。API status ok と表示されています

アセットと保護されたハンドラーをデプロイする

このステップでは、修正済みの構成だけを学習用アカウントにデプロイします。現在のローカルジョブを、jobs で確認した実際の番号を使って停止してください。

jobs
kill %1
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read

表示されたデバイスリンクとコードを、ログイン済みのブラウザで完了します。既存の Wrangler 権限と Background Access を確認し、学習用アカウントを選択してください。ターミナルの処理が完了するまで待ちます。

npx wrangler whoami --json

実際のアカウント名と ID を確認し、下の YOUR_ACCOUNT_ID をその ID に置き換えます。元のリソース名と修正済みのアセット設定はそのまま使用してください。

cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "account_id": "YOUR_ACCOUNT_ID",
  "workers_dev": true,
  "preview_urls": false,
  "assets": {
    "directory": "./public",
    "binding": "ASSETS",
    "html_handling": "none",
    "not_found_handling": "none",
    "run_worker_first": true
  }
}
CONFIG
npx wrangler deploy

Wrangler は public ディレクトリをアップロードし、ハンドラーをデプロイします。表示された正確な workers.dev URL を下の変数にコピーします。学習用アカウントに既にあるサブドメインを再利用してください。初回利用時は、Wrangler が表示する利用可能なサブドメインの案内に従います。

APP_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$APP_URL/staff/handbook.html"

シークレットをアップロードする前は、このルートは 503 の staff_unconfigured を返します。これはハンドラーが先に実行され、失敗時に安全側へ倒れるためです。.dev.vars はローカル設定であり、デプロイによってアップロードされていません。最初のホスト名の反映に時間がかかる場合は、継続的なエラーを調査する前に、少し待ってから再試行してください。

npx wrangler secret bulk .dev.vars
npx wrangler secret list

値を表示せずに、STAFF_TOKENsecret_text として一覧にあることを確認します。シークレットのデプロイがすべての配信場所に反映されるまで、少し時間がかかることがあります。次のリクエストがまだ staff_unconfigured を返す場合は、10 秒待ってから最大 2 分間、リクエストを繰り返してください。認証情報なしでは安定して 401、認証情報ありでは 200 になることを確認してから検証を使用します。継続的に一致しない場合は調査が必要です。最終結果として 503 を受け入れたり、検証を通すために認証ポリシーを変更したりしないでください。

curl -i "$APP_URL/"
curl -i "$APP_URL/api/health"
curl -i "$APP_URL/staff/handbook.html"
curl -i "$APP_URL/staff/handbook.html" -H "Authorization: Bearer $STAFF_TOKEN"
curl -i "$APP_URL/missing-page" -H "Sec-Fetch-Mode: navigate"

公開 HTML、ヘルス JSON、認証情報なしでは 401、認証情報ありではハンドブック HTML、不明なページでは 404 が返ることを確認します。同じ Dashboard アカウントで Compute → Workers & Pages を開き、対象の Worker を見つけて、公開 URL を確認します。検証を使って、実際の所有者、デプロイ済みバインディング、アセットの内容、認証動作を確認してください。自分のブラウザで公開ホームページを開いても構いませんが、スタッフトークンを URL 経由で送信しないでください。この合成トークンによるゲートはルーティングの学習用であり、完全なスタッフ認証システムではありません。

Worker の Overview タブで、パンくずリストの名前と、デプロイ出力に表示されたリンク先の workers.dev アドレスを比較します。このスクリーンショットにある名前とサブドメインは例です。実際には、生成された名前とアカウントのサブドメインが表示されます。ここに表示されるのはデプロイ済みの公開アドレスであり、Web 8080 はローカル開発サーバーのプレビューです。この既存の Worker を開くために、別のアプリケーションを作成する必要はありません。

Overview に表示された、デプロイ済みヘルプセンター Worker と公開アドレス

ヘルプセンターのデプロイメントを削除する

このステップでは、認証された状態で、実験用 Worker と関連するアセットおよびシークレットバインディングを削除します。固有の名前とアカウントを確認してください。

cat wrangler.jsonc
npx wrangler delete

プロンプトに表示された実験用の正確な名前を確認し、y キーを 1 回だけ押します。Wrangler 4.131.1 では、削除後に文書化されているレガシー Workers Sites KV 認証エラーが表示される場合があります。権限を広げたり、そのメッセージを削除の証拠として使ったりしないでください。Workers & Pages を更新し、検証を使って、この名前が存在しないことを確認します。無関係なリソース、アカウント、アカウントの workers.dev サブドメインは保持してください。

ローカル認証情報を削除して接続を解除する

このステップでは、クラウドのクリーンアップを確認した後、ローカルの合成認証情報を削除し、この VM との接続を解除します。

rm .dev.vars
unset STAFF_TOKEN
npx wrangler logout
npx wrangler whoami --json

明示的に "loggedIn": false と表示されることを確認します。この構造化された結果が得られている場合、未認証状態による終了ステータスが 0 以外になるのは想定された動作です。検証を行ってから VM を終了してください。ブラウザのログイン状態が残っていても構いません。VM の終了やログアウトによって、クラウドリソースが自動的に削除されることはありません。

まとめ

アセット優先のルーティングを確認し、その後 Worker を先に実行することで、アプリケーションのレスポンスと認証を一致するファイルより前に処理しました。提供されたヘルプセンターの外枠には、公開 HTML、CSS、ブラウザ用 JavaScript を残しながら、明示的なパス処理によって認証されていないスタッフリクエストと不明なルートをブロックしました。エンコードされたパスとブラウザ形式のナビゲーションをテストし、修正済みサイトをシークレットの別アップロードとともにデプロイしてから、削除とログアウトを確認しました。

静的ファイルとアプリケーションポリシーが同じホスト名を共有する場合は、ルーティングの順序を意図的に選択してください。ローカルのレスポンスだけでは、デプロイ済みの構成やアカウントの所有者を証明できません。