第15章 ミニ作品で総仕上げしよう 🏁🎉☁️🤖
2026年4月15日時点の公式情報で見ると、いまの Cloudflare 学習の王道は、C3 で土台を作る → ローカルでは Wrangler か Cloudflare Vite plugin で動かす → テストは Workers Vitest integration → ログは Workers Logs → 必要に応じて Workers AI と AI Gateway を足す、という流れです。しかも React 用の公式テンプレートは、React SPA + Worker API + Cloudflare Vite plugin という形で最初からかなり学習しやすく、総仕上げの題材にぴったりです。(Cloudflare Docs)
この章では、「AI文章しごとメモ」 という小さな作品を作ります ✨ ブラウザで文章を入力すると、Worker がそれを受け取り、整形したり、要約したり、結果を画面へ返したりします。さらに、ログを見る・テストを書く・公開前に preview するところまでまとめて体験します。 「大きな作品を作る」ことよりも、安全に確認しながら育てる流れを自分の手で1周できることが、この章のいちばん大事なゴールです 🌸
この章で作るもの 🎨🧩
完成イメージはこんな感じです。
- React の画面で文章を入力する ✍️
- Worker の /api/format に送って、文を整える 🧹
- Worker の /api/summary に送って、Workers AI で要約する 🤖
- 失敗したらログで追う 🔎
- Vitest で最低限の安心を作る ✅
- preview してから deploy する 🚀
この題材がよいのは、画面・API・bindings・AI・ログ・テストを全部、小さなサイズで触れるからです。Cloudflare の React テンプレートは、Worker API と SPA をまとめたフルスタック構成を C3 から作れますし、ローカル開発でも Vite plugin によって Workers runtime にかなり近い形で確認できます。さらに、通常の bindings はローカルでシミュレートされますが、AI binding だけは常にリモート実行になるので、「どこまでローカルで、どこから本物か」を学ぶ題材としてもすごく優秀です。(Cloudflare Docs)

この章の到達目標 🎯📘
この章を終えるころには、次の状態を目指します。
- C3 で Cloudflare 向けの小さな作品を作れる
- React 画面から Worker API を呼べる
- Workers AI を binding 経由で使える
- Vitest で最低限の確認を書ける
- ログを見て失敗を追える
- preview と deploy の違いを理解して公開できる
成果物 📦🌟
この章の成果物は、こんなセットです。
-
React の入力画面
-
Worker の API 3本
- /api/health
- /api/format
- /api/summary
-
Workers AI binding
-
観測用のログ
-
最低2本のテスト
-
公開前チェックリスト
15-1 まずは土台を作ろう 🛠️🌱
今回の作品は、React を使うので、公式の React テンプレートから始めるのがいちばん素直です。Cloudflare 公式ガイドでも、React + Vite の構成は React SPA + Cloudflare Workers API + Cloudflare Vite plugin をまとめて scaffold する形になっています。フロント中心の開発では Vite plugin が特に相性よく、HMR も効いて気持ちよく進められます。(Cloudflare Docs)
npm create cloudflare@latest -- ai-text-lab --framework=react
cd ai-text-lab
npm run dev
このテンプレートでは、ローカル開発時に Vite を使いつつ、Worker 側は Workers runtime の中で動きます。つまり、ただの「見た目だけの開発サーバー」ではなく、Cloudflare 本番にかなり近い感覚で進められます。(Cloudflare Docs)
15-2 生成されたファイルをざっくり読もう 🗂️👀
React テンプレートの簡略イメージはこんな感じです。
ai-text-lab/
src/
App.tsx
worker/
index.ts
index.html
vite.config.ts
wrangler.jsonc
役割はとてもシンプルです 😊
- src/App.tsx 画面側。入力欄やボタンを書く場所です。
- worker/index.ts バックエンド API。React から呼ばれる本体です。
- vite.config.ts Cloudflare Vite plugin を読み込む設定です。
- wrangler.jsonc Worker 名、entry、bindings、observability などを書く中心ファイルです。
Cloudflare 公式の React ガイドでは、worker/index.ts がバックエンド API、src/App.tsx がそれを呼ぶフロント、vite.config.ts が Cloudflare Vite plugin を使う設定、という構成が示されています。さらに SPA ルーティング用の設定も入っていて、フロントのルート処理と Worker API の役割分担がしやすくなっています。(Cloudflare Docs)
15-3 Worker API を作ろう ☁️⚙️
ここでは Worker 側に3つの入口を作ります。
- /api/health:生存確認
- /api/format:文章整形
- /api/summary:AI 要約

まずは worker/index.ts をこんな感じにします。
type Env = {
AI: {
run: (model: string, input: Record<string, unknown>) => Promise<any>;
};
};
function normalizeText(text: string): string {
return text
.replace(/\r\n/g, "\n")
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
.join("\n")
.replace(/[ \t]+/g, " ")
.trim();
}
function localFallbackSummary(text: string): string {
const parts = text
.split(/[。.!?]/)
.map((v) => v.trim())
.filter(Boolean)
.slice(0, 3);
return parts.length > 0 ? parts.join(" / ") : "要約できる文章がありません。";
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
console.log("request_path", url.pathname);
if (url.pathname === "/api/health") {
return Response.json({
ok: true,
message: "worker is running",
});
}
if (url.pathname === "/api/format" && request.method === "POST") {
const body = (await request.json()) as { text?: string };
const cleaned = normalizeText(String(body.text ?? ""));
return Response.json({
cleaned,
length: cleaned.length,
});
}
if (url.pathname === "/api/summary" && request.method === "POST") {
const body = (await request.json()) as { text?: string };
const cleaned = normalizeText(String(body.text ?? ""));
if (!cleaned) {
return Response.json(
{ error: "文章を入力してください。" },
{ status: 400 }
);
}
try {
const result = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
prompt:
"次の文章を日本語で3行以内に要約してください。箇条書きではなく自然文で返してください。\n\n" +
cleaned,
});
const summary =
typeof result === "string"
? result
: typeof result?.response === "string"
? result.response
: JSON.stringify(result);
return Response.json({
summary,
source: "workers-ai",
});
} catch (error) {
console.error("ai_summary_failed", error);
return Response.json({
summary: localFallbackSummary(cleaned),
source: "local-fallback",
});
}
}
return new Response("Not Found", { status: 404 });
},
};
Workers AI は binding を作ると env.AI から呼べる形になっていて、公式 docs でも env.AI.run() でモデルを実行する流れが示されています。また、Cloudflare のローカル開発では多くの bindings はローカルシミュレーションですが、AI bindings だけはリモートで実行されます。

なので、今回の /api/summary は「ローカルで動かしているのに AI だけ本物に飛ぶ」という、Cloudflare らしい学びになります。(Cloudflare Docs)
15-4 wrangler.jsonc に AI と observability を足そう 🔧🤖📈
React テンプレートの wrangler.jsonc に、学習用として次の2つを明示しておくのがおすすめです。
- observability.enabled
- ai.binding
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "ai-text-lab",
"main": "worker/index.ts",
"compatibility_date": "2026-04-15",
"observability": {
"enabled": true
},
"ai": {
"binding": "AI"
}
}
Wrangler 設定では observability や assets、各種 bindings をまとめて扱えます。Workers Logs は新規 Worker で既定有効ですが、教材では明示しておくと「何を有効にしたのか」が見えやすくて学びやすいです。Workers AI も wrangler ファイルまたはダッシュボードから binding を作る形が公式導線です。(Cloudflare Docs)
15-5 React 画面から API を呼ぼう 🌐💬
つぎに src/App.tsx をシンプルに作ります。 「入力 → 整形」「入力 → AI 要約」の2ボタンがあれば十分です。
import { useState } from "react";
export default function App() {
const [text, setText] = useState("");
const [result, setResult] = useState("");
const [loading, setLoading] = useState(false);
async function callApi(path: string) {
setLoading(true);
setResult("");
try {
const response = await fetch(path, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ text }),
});
const data = await response.json();
if (!response.ok) {
setResult(data.error ?? "エラーが発生しました。");
return;
}
if (path === "/api/format") {
setResult(data.cleaned);
} else {
setResult(data.summary);
}
} catch (error) {
setResult("通信に失敗しました。");
console.error(error);
} finally {
setLoading(false);
}
}
return (
<main style={{ maxWidth: 800, margin: "40px auto", padding: 16 }}>
<h1>AI文章しごとメモ ☁️🤖</h1>
<p>文章を整形したり、要約したりする小さな Cloudflare 作品です。</p>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
rows={12}
style={{ width: "100%", marginTop: 12 }}
placeholder="ここに文章を入れてください"
/>
<div style={{ display: "flex", gap: 12, marginTop: 12 }}>
<button onClick={() => callApi("/api/format")} disabled={loading}>
整形する 🧹
</button>
<button onClick={() => callApi("/api/summary")} disabled={loading}>
AIで要約する 🤖
</button>
</div>
<section style={{ marginTop: 24 }}>
<h2>結果</h2>
<pre style={{ whiteSpace: "pre-wrap" }}>{result}</pre>
</section>
</main>
);
}
この形にしておくと、Worker API を先に作って、画面はあとから載せるという順番でも進められます。初心者が詰まりにくいのは、実はこの順番です 😊
15-6 ローカル確認の流れを1本にしよう 🔄🧪
動かすときの基本ループはこれです。
- npm run dev で立ち上げる
- 画面から /api/format を試す
- 画面から /api/summary を試す
- ターミナルのログを見る
- 詰まったらコードを少しだけ直す
- もう一度試す
Cloudflare Vite plugin は HMR を使えるので、React 側の変更がすばやく反映されます。しかも Worker は Workers runtime で動くので、ローカルでも「Cloudflare っぽさ」をかなり保ったまま確認できます。さらに Vite plugin は vite preview にも対応しているので、build 結果を本番前に確認しやすいのも強みです。(Cloudflare Docs)
ここでのコツは、いきなり AI から触らないことです 🌱 まず /api/health、次に /api/format、最後に /api/summary の順で試すと、失敗箇所がかなり切り分けやすくなります。
15-7 テストを2本だけ書こう ✅🧠
Cloudflare 公式は、いまの Workers / Pages Functions のテスト方法として Workers Vitest integration を強くおすすめしています。これは Cloudflare の custom pool を使って、Vitest を Workers runtime の中で動かせる仕組みです。

公式の「最初のテスト」ガイドでは、Vitest 4.1 以上と @cloudflare/vitest-pool-workers が案内されています。(Cloudflare Docs)
まず、入っていなければ追加します。
npm i -D vitest@^4.1.0 @cloudflare/vitest-pool-workers
npx wrangler types
vitest.config.ts はこんな感じです。
import { defineConfig } from "vitest/config";
import { cloudflareTest } from "@cloudflare/vitest-pool-workers";
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: {
configPath: "./wrangler.jsonc",
},
}),
],
test: {
include: ["test/**/*.test.ts"],
},
});
公式設定でも、cloudflareTest() を plugins に入れ、wrangler の configPath を参照する形が基本です。(Cloudflare Docs)
つぎに test/worker.test.ts を作ります。
import { describe, expect, it } from "vitest";
import { exports } from "cloudflare:workers";
describe("worker api", () => {
it("health が 200 を返す", async () => {
const response = await exports.default.fetch("https://example.com/api/health");
const data = await response.json<{ ok: boolean }>();
expect(response.status).toBe(200);
expect(data.ok).toBe(true);
});
it("format が文章を整形する", async () => {
const request = new Request("https://example.com/api/format", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
text: " Cloudflare Workers を 勉強中です。 ",
}),
});
const response = await exports.default.fetch(request);
const data = await response.json<{ cleaned: string }>();
expect(response.status).toBe(200);
expect(data.cleaned).toContain("Cloudflare Workers を 勉強中です。");
});
});
Workers Vitest integration では、cloudflare:workers や cloudflare:test から helper を使えます。たとえば exports.default.fetch() で main Worker の fetch handler を直接叩けるので、今回のような API テストがかなり書きやすいです。(Cloudflare Docs)
ここで大事なのは、「全部をテストしよう」としないことです 🙌 総仕上げではまず、
- 生きているか
- 主要ルートが返るか
- 入力が空のとき壊れないか
この3点だけでも十分に価値があります。
15-8 ログで追える作品にしよう 📈🔍
ログは、ただのメモではありません。失敗の場所を絞るための地図です。Cloudflare の Workers Logs では、invocation logs、custom logs、errors、uncaught exceptions を集めてダッシュボードで見られます。しかも新規 Worker では observability が既定有効です。(Cloudflare Docs)
今回の作品なら、こんなログがちょうどいいです。
console.log("request_path", url.pathname);
console.log("input_length", cleaned.length);
console.error("ai_summary_failed", error);
ただし、ログは多ければよいわけではありません。Cloudflare の limits では、1リクエストあたりのログデータ上限は 256KB です。

出しすぎると、途中から記録されません。だから、全部を吐くより、分岐名・件数・失敗理由を短く出すのがコツです。(Cloudflare Docs)
15-9 Copilot を「直してもらう人」ではなく「整理してくれる相棒」にしよう 🤖💡
いまの GitHub Copilot は、agent mode と MCP を組み合わせることで、外部情報やツールに触れながら動けるようになっています。GitHub Docs でも、MCP は Copilot を他システムとつなぐ仕組みとして説明されていますし、Cloudflare 側も docs MCP と observability MCP を案内しています。Cloudflare Docs MCP は Workers の知識参照用、observability MCP はログや例外の確認に使える導線です。(GitHub Docs)
この章では、Copilot にこんなお願いをすると相性がよいです ✨
- 「この Worker の責務を3つに分けて説明して」
- 「この failing test の原因候補を優先順で出して」
- 「wrangler.jsonc の binding 設定を見て不足を教えて」
- 「この console.error の出し方を、ログが見やすい形へ改善して」
ポイントは、完成コードを丸投げしないことです。 「説明」「候補比較」「原因整理」をやらせると、学習効果がぐっと上がります 🌸
15-10 AI Gateway を“次の一歩”として足そう 🚪🤖☁️
作品が動いたら、次の一歩として AI Gateway を考えるとすごく今っぽいです。AI Gateway は 全プランで利用可能で、公式では analytics、logging、caching、rate limiting、request retries、model fallback などをまとめて扱えると案内されています。しかも「まずは1行で始めやすい」とされていて、Cloudflare の AI まわりを観測・制御する入口としてかなり使いやすいです。(Cloudflare Docs)

さらに Universal Endpoint では、Workers AI を含む複数 provider に対して共通入口で投げることができ、retry や fallback の設計も入れやすいです。なので本章の完成後は、/api/summary をそのまま AI Gateway 経由版へ差し替える、という発展が自然です。(Cloudflare Docs)
15-11 公開前に preview してから deploy しよう 🚀🧭
最後は公開です。 おすすめ順はこれです。
npm run build
npm run preview
npx wrangler deploy

Cloudflare Vite plugin の公式ガイドでは、preview で build 結果を Workers runtime で確認でき、React テンプレートのガイドでも、最終的に workers.dev サブドメイン や Custom Domain へ deploy できる流れが示されています。(Cloudflare Docs)
ここでの気持ちは、「早く公開したい!」よりも、最後にもう1回だけ冷静に見るです 😊 その1回で、かなり事故が減ります。
公開前チェックリスト 📝✅
公開前は、これだけ見れば十分です。
- /api/health が返る
- /api/format が空入力でも変に落ちない
- /api/summary が失敗時に fallback する
- 主要テストが green
- 秘密情報をコードへ直書きしていない
- ログがうるさすぎない
- preview で画面崩れがない
- ボタン連打時に極端に壊れない
この章のミニ課題 🎓✨
余力があれば、次のどれか1つを足してみてください。
- D1 で履歴保存 📚 入力文と要約結果を保存する
- AI Gateway 経由に差し替える 🚪 AI の観測や fallback を学ぶ
- モデル選択UIを足す 🎛️ 要約向け・説明向けを切り替える
- Next.js 版に移植してみる 🌈 Cloudflare は C3 から Next.js を scaffold でき、Workers への deploy は OpenNext adapter 経由で案内されています。ローカル開発は npm run dev、Cloudflare adapter を含めた確認は npm run preview が基本導線です。(Cloudflare Docs)
この章で身についたこと 💪🎉
この章まで来たら、もう「Cloudflare をちょっと触った」ではありません。 ちゃんと、
- 作る ✍️
- ローカルで動かす 💻
- AI をつなぐ 🤖
- テストする ✅
- ログで追う 🔎
- preview して deploy する 🚀
という、開発の1周を自分で回せています。
それがいちばん大きいです 🌟 Cloudflare 学習は、ここから先に差がつきます。 ただ書いて終わる人ではなく、確かめながら育てられる人になれているからです ☁️🛠️✨
必要なら次に、この第15章をベースにした 「学習目標・演習課題・提出物・所要時間つきの教材設計版」 へ、そのまま落とし込んでいきます。