静的サイト生成(SSG)アプリケーションは、あらかじめビルド(「事前レンダリング」)されることが中心の Web アプリケーションです。Gatsby や Docusaurus などのフレームワークで作ることが多いです。これらのフレームワークのビルドでは、多数の HTML ファイルと、付随するクライアント側リソース(JavaScript バンドル、CSS スタイルシート、画像、フォントなど)が出力されます。データは静的か、ビルド時に取得して HTML に組み込むか、クライアントが API へクライアント側リクエストで取得します。
SSG フレームワークでは、カスタム 404 ページを作れることがよくあります。
静的サイト生成アプリケーションを Workers にデプロイするには、Wrangler 設定ファイル で assets.directory を設定します。必要に応じて assets.not_found_handling と assets.html_handling も設定します。
{
"name": "my-worker",
// Set this to today's date
"compatibility_date": "2026-09-20",
"assets": {
"directory": "./dist/",
"not_found_handling": "404-page",
"html_handling": "auto-trailing-slash"
}
}name = "my-worker"
# Set this to today's date
compatibility_date = "2026-09-20"
[assets]
directory = "./dist/"
not_found_handling = "404-page"
html_handling = "auto-trailing-slash"assets.html_handling のデフォルトは auto-trailing-slash で、通常はこれで期待どおりの動作になります。個別ファイル(foo.html など)は末尾スラッシュ なし で配信され、フォルダーのインデックスファイル(foo/index.html など)は末尾スラッシュ あり で配信されます。あるいは、HTML ページへのリクエストで末尾スラッシュを強制する(force-trailing-slash)か、末尾スラッシュを除く(drop-trailing-slash)こともできます。
assets.not_found_handling を 404-page にすると、静的アセットに対する Workers のデフォルト配信動作が上書きされます。受信リクエストが assets.directory 内のファイルに一致しない場合、Workers は最も近い 404.html の内容を 404 Not Found ステータスで返します。
Worker スクリプト(main)があり、assets.not_found_handling を設定しており、かつ assets_navigation_prefers_asset_serving compatibility flag を使っている(または compatibility date を 2025-04-01 以降にしている)場合、ナビゲーションリクエスト は Worker スクリプトを呼び出しません。ナビゲーションリクエスト は、Sec-Fetch-Mode: navigate ヘッダー付きのリクエストです。ブラウザーはページへ遷移するときに、このヘッダーを自動で付けます。これにより Worker スクリプトの課金対象の呼び出しが減ります。クライアント側が中心のアプリケーションでは、そうしなければ Worker スクリプトが不要なほど頻繁に呼び出されるため、特に有効です。
ナビゲーションリクエストから Worker スクリプトへ値を渡す必要がある場合があります。たとえば OAuth コールバックとして動作させるとき、/oauth/callback?code=... のようなルートへのリクエストを想定することがあります。assets_navigation_prefers_asset_serving フラグがある場合、Worker スクリプトではなく HTML アセットが配信されます。この場合は、該当ルート向けのクライアントアプリケーション内、またはエンドポイント専用の簡易 HTML ファイルで、クライアント側の JavaScript からサーバーへ値を渡すことを推奨します。
<!DOCTYPE html>
<html>
<head>
<title>OAuth callback</title>
</head>
<body>
<p>Loading...</p>
<script>
(async () => {
const response = await fetch("/api/oauth/callback" + window.location.search);
if (response.ok) {
window.location.href = '/';
} else {
document.querySelector('p').textContent = 'Error: ' + (await response.json()).error;
}
})();
</script>
</body>
</html>import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/api/oauth/callback") {
const code = url.searchParams.get("code");
const sessionId =
await exchangeAuthorizationCodeForAccessAndRefreshTokensAndPersistToDatabaseAndGetSessionId(
code,
);
if (sessionId) {
return new Response(null, {
headers: {
"Set-Cookie": `sessionId=${sessionId}; HttpOnly; SameSite=Strict; Secure; Path=/; Max-Age=86400`,
},
});
} else {
return Response.json(
{ error: "Invalid OAuth code. Please try again." },
{ status: 400 },
);
}
}
return new Response(null, { status: 404 });
}
}import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
async fetch(request: Request) {
const url = new URL(request.url);
if (url.pathname === "/api/oauth/callback") {
const code = url.searchParams.get("code");
const sessionId = await exchangeAuthorizationCodeForAccessAndRefreshTokensAndPersistToDatabaseAndGetSessionId(code);
if (sessionId) {
return new Response(null, {
headers: {
"Set-Cookie": `sessionId=${sessionId}; HttpOnly; SameSite=Strict; Secure; Path=/; Max-Age=86400`,
},
});
} else {
return Response.json(
{ error: "Invalid OAuth code. Please try again." },
{ status: 400 }
);
}
}
return new Response(null, { status: 404 });
}
}Vite ベースの SPA フレームワークを使っている場合は、Vite ネイティブな開発体験を提供する Vite plugin が役立つことがあります。
多くの場合、assets.not_found_handling を 404-page にすれば期待どおりの動作になります。独自フレームワークを作っている場合や特別な要件がある場合は、次の図でルーティング判断の詳細を確認できます。
ルーティング判断の全体図
flowchart
Request@{ shape: stadium, label: "受信リクエスト" }
Request-->RunWorkerFirst
RunWorkerFirst@{ shape: diamond, label: "Worker スクリプトを先に実行しますか?" }
RunWorkerFirst-->|リクエストが run_worker_first のパスに一致|WorkerScriptInvoked
RunWorkerFirst-->|リクエストが run_worker_first の除外パスに一致|AssetServing
RunWorkerFirst-->|一致なし|RequestMatchesAsset
RequestMatchesAsset@{ shape: diamond, label: "リクエストがアセットに一致しますか?" }
RequestMatchesAsset-->|はい|AssetServing
RequestMatchesAsset-->|いいえ|WorkerScriptPresent
WorkerScriptPresent@{ shape: diamond, label: "Worker スクリプトがありますか?" }
WorkerScriptPresent-->|いいえ|AssetServing
WorkerScriptPresent-->|はい|RequestNavigation
RequestNavigation@{ shape: diamond, label: "ナビゲーションリクエストですか?" }
RequestNavigation-->|いいえ|WorkerScriptInvoked
WorkerScriptInvoked@{ shape: rect, label: "Worker スクリプトを実行" }
WorkerScriptInvoked-.->|アセットバインディング|AssetServing
RequestNavigation-->|はい|AssetServing
subgraph Asset serving
AssetServing@{ shape: diamond, label: "リクエストがアセットに一致しますか?" }
AssetServing-->|はい|AssetServed
AssetServed@{ shape: stadium, label: "**200 OK**<br />アセットを配信" }
AssetServing-->|いいえ|NotFoundHandling
subgraph 404-page
NotFoundHandling@{ shape: rect, label: "リクエストを ../404.html に書き換え" }
NotFoundHandling-->404PageExists
404PageExists@{ shape: diamond, label: "HTML ページがありますか?" }
404PageExists-->|はい|404PageServed
404PageExists-->|いいえ|404PageAtIndex
404PageAtIndex@{ shape: diamond, label: "ルートの /404.html へのリクエストですか?" }
404PageAtIndex-->|はい|Generic404PageServed
404PageAtIndex-->|いいえ|NotFoundHandling
Generic404PageServed@{ shape: stadium, label: "**404 Not Found**<br />空ボディのレスポンスを配信" }
404PageServed@{ shape: stadium, label: "**404 Not Found**<br />404.html を配信" }
end
end課金対象になるのは、Worker スクリプトが呼び出された場合だけです。そこから、アセットバインディングを使ってアセットを配信できます(上図の点線)。
アセットの一致方法の詳細は、HTML 処理のドキュメント を参照してください。