Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

静的アセット

最終更新 Markdown で表示Agent セットアップ

Dynamic Workers は、Worker のコードと一緒に、HTML ページ、JavaScript バンドル、画像などの静的アセットを配信できます。Dynamic Worker でフルスタックアプリケーションを配信したいときに使います。

Dynamic Workers の静的アセットは、通常の Workers の静的アセット とは動きが異なります。デプロイ時にアセットをアップロードするのではなく、実行時に Worker Loader の get() コールバックで渡し、R2、KV、または別のストレージバックエンドから取得します。

仕組み

Dynamic Workers の静的アセットを設定する要素は 3 つです。

  1. アセットを保存する — 静的ファイルを KV 名前空間へアップロードし、プロジェクト ID とパス名でキーを分けます。
  2. ローダー Worker でアセットバインディングを定義する — KV から静的ファイルを読み、正しいヘッダーを付けて返すクラスを作ります。
  3. バインディングを Dynamic Worker へ渡す — Dynamic Worker は env.ASSETS.fetch(request) を呼んで静的ファイルを配信します。

静的アセットを保存する

静的アセットは KV 名前空間に保存し、プロジェクト ID で分けます。各プロジェクトのファイルは互いに隔離されます。

project/{projectId}/assets/index.html      →  file content
project/{projectId}/assets/app.js          →  file content
project/{projectId}/manifest               →  asset manifest

ユーザーがプラットフォームのアップロード API でプロジェクトをデプロイしたら、各ファイルをパス名の下に KV へ保存します。

await env.KV_ASSETS.put(`project/${projectId}/assets${pathname}`, fileContent);

マニフェストも保存する必要があります。マニフェストは、どのファイルが存在し、それぞれのコンテンツタイプが何かをアセットハンドラーへ伝える対応表です。@cloudflare/worker-bundlerbuildAssetManifest() を使い、アセットから生成します。

import { buildAssetManifest } from "@cloudflare/worker-bundler";

const assets = {
	"/index.html": htmlContent,
	"/app.js": jsContent,
	"/style.css": cssContent,
};

const manifest = await buildAssetManifest(assets);

await env.KV_ASSETS.put(
	`project/${projectId}/manifest`,
	JSON.stringify(manifest),
);
import { buildAssetManifest } from "@cloudflare/worker-bundler";

const assets = {
	"/index.html": htmlContent,
	"/app.js": jsContent,
	"/style.css": cssContent,
};

const manifest = await buildAssetManifest(assets);

await env.KV_ASSETS.put(
	`project/${projectId}/manifest`,
	JSON.stringify(manifest),
);

ローダー Worker にバインディングを追加する

アセットを保存した KV 名前空間へのアクセスを、ローダー Worker に与えます。

{
	"worker_loaders": [{ "binding": "LOADER" }],
	"kv_namespaces": [
		{
			"binding": "KV_ASSETS",
			"id": "<your-kv-namespace-id>",
		},
	],
}
[[worker_loaders]]
binding = "LOADER"

[[kv_namespaces]]
binding = "KV_ASSETS"
id = "<your-kv-namespace-id>"

アセットバインディングを定義する

ローダー Worker で WorkerEntrypoint を継承するクラスを作り、fetch() メソッドを定義します。WorkerEntrypoint により、Dynamic Worker から RPC でこのメソッドを呼べます。Dynamic Worker が env.ASSETS.fetch(request) を呼ぶと、このメソッドがローダー Worker で実行されます。KV バインディングとアセット配信のロジックは、こちらに置きます。

クラスは projectId という prop を受け取り、どのプロジェクトのアセットを探すかを決めます。fetch() が呼ばれると、次を行います。

  1. プロジェクトのアセットマニフェストを KV から読み込みます。
  2. リクエストのパス名をファイルへ解決します。
  3. ファイルの内容を KV から取得します。
  4. 正しい Content-Type ヘッダーを付けた Response を返します。

@cloudflare/worker-bundler で静的アセット配信を処理する

リクエストパスとファイルの照合、コンテンツタイプの判定、キャッシュヘッダーの設定を自分で書く代わりに、@cloudflare/worker-bundler パッケージで静的アセット配信を処理できます。fetch() メソッドでは、handleAssetRequest() に次の 2 つを渡します。

  • マニフェスト: アップロード時に KV へ保存した、パスとコンテンツタイプの対応表です。buildAssetManifest() で作ります。handleAssetRequest() は、どのファイルが存在し、コンテンツタイプが何かをこれで判断します。
  • ストレージオブジェクト: handleAssetRequest() が KV 名前空間からファイルを読む方法です。メソッドは get(pathname) の 1 つで、指定したファイルパスの内容を読んで返します。

handleAssetRequest() は、マニフェストに一致するファイルがあれば、コンテンツタイプとキャッシュ用の正しいヘッダーを付けて配信します。

import { WorkerEntrypoint } from "cloudflare:workers";
import { handleAssetRequest } from "@cloudflare/worker-bundler";

export class AssetBinding extends WorkerEntrypoint {
	async fetch(request) {
		const { projectId } = this.ctx.props;

		// Load the project's asset manifest from KV
		const manifest = await this.env.KV_ASSETS.get(
			`project/${projectId}/manifest`,
			{ type: "json", cacheTtl: 300 },
		);

		if (!manifest) {
			return new Response("No assets found", { status: 404 });
		}

		// Storage object — handleAssetRequest calls get() to
		// read file content when it needs to serve an asset
		const storage = {
			async get(pathname) {
				return this.env.KV_ASSETS.get(
					`project/${projectId}/assets${pathname}`,
					{ type: "arrayBuffer", cacheTtl: 86_400 },
				);
			},
		};

		const response = await handleAssetRequest(request, manifest, storage);
		return response ?? new Response("Not Found", { status: 404 });
	}
}
import { WorkerEntrypoint } from "cloudflare:workers";
import { handleAssetRequest } from "@cloudflare/worker-bundler";

export class AssetBinding extends WorkerEntrypoint {
	async fetch(request: Request) {
		const { projectId } = this.ctx.props;

		// Load the project's asset manifest from KV
		const manifest = await this.env.KV_ASSETS.get(
			`project/${projectId}/manifest`,
			{ type: "json", cacheTtl: 300 },
		);

		if (!manifest) {
			return new Response("No assets found", { status: 404 });
		}

		// Storage object — handleAssetRequest calls get() to
		// read file content when it needs to serve an asset
		const storage = {
			async get(pathname: string) {
				return this.env.KV_ASSETS.get(
					`project/${projectId}/assets${pathname}`,
					{ type: "arrayBuffer", cacheTtl: 86_400 },
				);
			},
		};

		const response = await handleAssetRequest(request, manifest, storage);
		return response ?? new Response("Not Found", { status: 404 });
	}
}

AssetBinding をエクスポートすると、ローダー Worker の fetch() ハンドラーで ctx.exports から使えるようになります。ctx はハンドラーの第 3 引数で、requestenv のあとです。次の手順で、これを Dynamic Worker へ渡します。

アセットバインディングを Dynamic Worker へ渡す

get() を呼んで Dynamic Worker を作るとき、env オブジェクトに AssetBinding を含めます。Dynamic Worker はこれを使って静的ファイルを配信します。前の手順で定義した AssetBinding クラスを参照するには、ctx.exports.AssetBinding() を使い、どのプロジェクトのアセットを配信するかを知らせる projectId を prop として渡します。動きはカスタムバインディングと同じです。props でクラスへ情報を渡し、実行時にクラスは this.ctx.props で読みます。

export default {
	async fetch(request, env, ctx) {
		const projectId = getProjectIdFromRequest(request);

		const worker = env.LOADER.get(projectId, async () => {
			const serverCode = await loadServerCode(projectId);

			return {
				mainModule: "index.js",
				modules: {
					"index.js": { js: serverCode },
				},
				compatibilityDate: "2026-09-20",
				env: {
					ASSETS: ctx.exports.AssetBinding({
						props: { projectId },
					}),
				},
			};
		});

		return await worker.getEntrypoint().fetch(request);
	},
};
export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		const projectId = getProjectIdFromRequest(request);

		const worker = env.LOADER.get(projectId, async () => {
			const serverCode = await loadServerCode(projectId);

			return {
				mainModule: "index.js",
				modules: {
					"index.js": { js: serverCode },
				},
				compatibilityDate: "$today",
				env: {
					ASSETS: ctx.exports.AssetBinding({
						props: { projectId },
					}),
				},
			};
		});

		return await worker.getEntrypoint().fetch(request);
	},
};

Dynamic Worker から見ると、ASSETS はバインディングです。AssetBinding に定義したメソッドなので、env.ASSETS.fetch(request) を呼べます。Dynamic Worker がそのメソッドを呼ぶと、ローダー Worker で実行され、AssetBinding クラスが KV からマニフェストとファイル内容を読みます。

Dynamic Worker でアセットバインディングを使う

Dynamic Worker から見ると、env.ASSETS はほかのバインディングと同じように使えます。ユーザーはサーバーコードを書き、env.ASSETS.fetch() を呼んで静的ファイルを配信します。

// Inside the Dynamic Worker
export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		// Handle API routes directly
		if (url.pathname.startsWith("/api/")) {
			return Response.json({ hello: "world" });
		}

		// Everything else — serve static assets
		return env.ASSETS.fetch(request);
	},
};
// Inside the Dynamic Worker
export default {
	async fetch(request: Request, env: Env) {
		const url = new URL(request.url);

		// Handle API routes directly
		if (url.pathname.startsWith("/api/")) {
			return Response.json({ hello: "world" });
		}

		// Everything else — serve static assets
		return env.ASSETS.fetch(request);
	},
};

Dynamic Worker が env.ASSETS.fetch(request) を呼ぶと、呼び出しは RPC 経由でローダー Worker の AssetBinding へ届きます。AssetBinding はマニフェストでファイルを探し、KV から読みます。Dynamic Worker 側でこれを扱う必要はありません。env.ASSETS.fetch(request) を呼べば、正しいヘッダー付きのファイルが返り、クライアントへ返せます。

役に立ちましたか?