Worker (API) の作成
Astro(Pages)とWorkerを最小構成で作成し、実際にデプロイして動作確認するための手順をまとめました。
この手順では、Astroをフロントエンド、**Workerをバックエンド(API)**として扱い、同一ドメインの /api/* ルートで連携させます。
1. Worker (API) の作成とデプロイ
まずはAPIとなるWorkerを作成します。
Bash
# 1. Workerプロジェクトの作成(C3を使用)
# 設定: "Hello World" worker / TypeScript: Yes / Deploy: Yes
npm create cloudflare@latest my-api-worker
# 2. プロジェクトディレクトリへ移動
cd my-api-worker
src/index.ts を編集して動作確認用レスポンスを返す:
TypeScript
export default {
async fetch(request, env, ctx): Promise<Response> {
return new Response(JSON.stringify({ message: "Hello from Worker!" }), {
headers: { "content-type": "application/json" },
});
},
} satisfies ExportedHandler;
デプロイ:
Bash
npm run deploy
[!NOTE]
デプロイ完了時に表示される
https://my-api-worker.xxxx.workers.devをメモしておいてください。
OK、ここは混乱しやすい最重要ポイントなので、完全に整理します👇
■ 結論(コマンド一覧)
✅ ① Pages(静的サイト / SSG)
👉 一番おすすめ(まずはこれ)
npm run build
npx wrangler pages deploy ./dist
または(設定済みなら)
npx wrangler pages deploy
✅ ② SSR(Workersで動かす)
👉 動的サイト・APIあり
npm run build
npx wrangler deploy
■ 違い(超重要)
| 項目 | Pages | SSR(Workers) |
|---|---|---|
| コマンド | pages deploy |
deploy |
| 出力 | 静的HTML | serverコード |
| フォルダ | dist/ |
dist/server/ |
| 用途 | ブログ・LP | API・ログイン |
■ Astro設定との対応
Pagesにする場合
// astro.config.mjs
export default {
output: "static"
}
👉 これで dist/ が生成
SSRにする場合
import cloudflare from '@astrojs/cloudflare';
export default {
adapter: cloudflare()
}
👉 dist/server/ が生成
■ よくあるミス(あなたがハマってた所)
❌ SSRなのに
wrangler pages deploy
❌ 静的なのに
wrangler deploy
👉 これでエラー地獄になる
■ 判断基準(迷ったらこれ)
👉 今のあなたなら👇
✔ Pages(静的)にする
-
シンプル
-
エラー少ない
-
速い
✔ SSRにするのは後
-
API必要になったら
-
ログイン機能やるとき
■ 一言で覚える
👉 Pages = HTML配る
👉 SSR = サーバー動かす
■ おすすめ最終形(プロ構成)
👉 将来的にはこれ👇
-
フロント → Astro(Pages)
-
API → Cloudflare Workers
2. Astro (Pages) の作成と設定
次に、Workerを呼び出すAstroプロジェクトを作成します。
Bash
# 1. Astroプロジェクトの作成
# 設定: "Empty" または "Sample" / TypeScript: Yes / Install dependencies: Yes
npm create astro@latest my-astro-app
# 2. ディレクトリへ移動
cd my-astro-app
# 3. Cloudflare Adapterの追加(SSRモードにするため)
npx astro add cloudflare
src/pages/index.astro を編集してAPIを叩く:
コード スニペット
---
// サーバーサイドでのフェッチ例
// デプロイ後は相対パス "/api/test" で動くようになります
---
<html>
<body>
<h1>Astro + Worker 連携テスト</h1>
<button id="fetchBtn">Workerからデータを取得</button>
<p id="result"></p>
<script>
document.getElementById('fetchBtn')?.addEventListener('click', async () => {
const res = await fetch('/api/test'); // 同一ドメインのルート経由
const data = await res.json();
const resultEl = document.getElementById('result');
if (resultEl) resultEl.innerText = data.message;
});
</script>
</body>
</html>
デプロイ:
Bash
# Pagesとしてデプロイ(GitHub連携が推奨ですが、コマンドでも可能)
npx wrangler pages deploy ./dist
3. Cloudflareコンソールでの紐付け(重要)
最後に、前回の回答で解説した 「Routes」 の設定を行います。
-
Cloudflareダッシュボード > Workers & Pages > my-api-worker を開く。
-
Settings > Domains & Routes > Add Route をクリック。
-
Route:
[Astroのドメイン]/api/*を入力。 -
Zone: 使用しているドメインを選択。
4. 動作確認コマンド
デプロイしたAstroのURLにアクセスし、ボタンを押して “Hello from Worker!” と表示されれば成功です。
ブラウザのデベロッパーツール(Networkタブ)で確認すると、リクエストが https://example.com/api/test へ飛んでおり、CORSエラーなしでデータが取得できているはずです。
5. ローカル開発での動作確認
ローカルで両方を同時に動かしてテストしたい場合は、wrangler の proxy 機能を使います。
Bash
# Astroをビルドしてプレビュー
npm run build
npx wrangler pages dev ./dist --proxy 8787
# 別ターミナルでWorkerを起動
cd ../my-api-worker
npx wrangler dev --port 8787
これにより、ローカルでも localhost:8888/api/* へのアクセスが Worker へ転送されます。
Cloudflare上でAstro(Pages)と、別体で用意したWorkerを「API連携」させる場合、基本的には**「同一ドメインの下にWorkerをぶら下げる」**形にするのが最もスムーズです(CORS対応が不要になるため)。
Cloudflareコンソールの「Routes(ルート)」設定を中心に、その手順を詳しく解説します。
1. 構成のイメージ
Astroプロジェクト(Pages)をメインのドメインで動かし、特定のパス(例: /api/*)へのリクエストだけをWorkerに飛ばす設定を行います。
-
Astro (Cloudflare Pages):
example.com/* -
Worker (API用):
example.com/api/*
2. Cloudflareコンソールでの「Routes」設定手順
Workerを特定のURL(パス)で動作させるには、Worker側の設定画面から操作します。
-
Cloudflareダッシュボードにログインし、左メニューの 「Workers & Pages」 を選択します。
-
連携させたい Worker(API側) をクリックして開きます。
-
上部タブの 「Settings(設定)」 > 「Domains & Routes(ドメインとルート)」 を選択します。
-
「Add(追加)」 ボタンをクリックし、「Route(ルート)」 を選択します。
-
以下の内容を入力して保存します。
-
Route:
example.com/api/*(example.comはあなたのドメインに置き換えてください) -
Zone: あなたのドメイン名を選択
-
[!IMPORTANT]
この設定を有効にするには、対象のドメインが CloudflareのDNS管理下(プロキシ済み/オレンジの雲マーク) である必要があります。
3. Astro側での呼び出し方
上記の設定が完了すると、Astroのフロントエンド(クライアントサイド)やSSRコードから、相対パスでAPIを叩けるようになります。
JavaScript
// Astroコンポーネントやスクリプト内
const response = await fetch('/api/user-data');
const data = await response.json();
なぜこの設定が良いのか?
-
CORS回避: 同じドメインとして扱われるため、面倒なCORS(Cross-Origin Resource Sharing)設定が一切不要になります。
-
Cookieの共有: セッション管理などでCookieを使用している場合、そのままWorkerに送信されます。
4. 注意点:Pages Functionsとの競合
Astroで adapter: cloudflare() を使用している場合、Astro自体も内部的にWorker(Pages Functions)として動いています。
-
優先順位: Cloudflareの仕様上、「WorkersのRoutes」は「Pages」よりも優先されます。
-
そのため、
/api/*というルートをWorkerに割り当てると、Astro側でsrc/pages/api/にファイルを作っていても、それは無視され、設定したWorkerが実行されます。
5. 代替案:Service Bindings(推奨)
もし「外部公開したくないAPI」をAstroから呼び出したい、あるいはもっと高速に連携させたい場合は、Routesではなく Service Bindings を使うのが現在のトレンドです。
-
Pagesの設定 > Functions > Service bindings に移動。
-
変数名(例:
API_WORKER)を決め、対象のWorkerを紐付ける。 -
Astroのサーバーサイドコード(SSR)で
context.locals.runtime.env.API_WORKER.fetch(request)のように直接呼び出す。
これでネットワークを介さず(インターネットに出ず)にWorkerを叩けます。
Cloudflare Pages(Astro)と Workers を「同一ドメインの特定パス(/api/*)」で共存させる仕組みと、そのメリットを詳しく解説します。
この構成は、モダンな Web 開発において 「BFF (Backend For Frontend)」 パターンを構築する際に非常に強力です。
1. 全体像とルーティングの優先順位
Cloudflare のインフラ内では、リクエストが届いた際に「どのサービスに割り振るか」の優先順位が決まっています。
-
Workers (Custom Routes): 最も優先度が高い。
-
Pages (Functions / Static Assets): Workers のルートに該当しない場合に処理される。
つまり、example.com/api/* という Route を Worker に割り当てると、Astro 側(Pages)に同じパスがあっても、Cloudflare は迷わず Worker へリクエストを飛ばします。
2. なぜこの構成にするのか?(3つのメリット)
① CORS (Cross-Origin Resource Sharing) の完全回避
通常、フロントエンド(a.com)から別ドメインの API(api.b.com)を叩く場合、ブラウザのセキュリティ制限により CORS の設定が必要です。
しかし、今回の設定では**ブラウザから見て「同じドメイン」**なので、複雑なヘッダー設定なしで通信が許可されます。
② セキュリティと Cookie の共有
ドメインが同じであるため、Astro 側で発行した HttpOnly 属性付きの Cookie を、Worker 側でもそのまま受け取ることができます。これにより、セッション認証やログイン状態の管理が非常に楽になります。
③ コストとパフォーマンスの最適化
-
Astro: 静的コンテンツの配信や、エッジでのレンダリング(SSR)に集中。
-
Worker: データベース操作、外部 API との通信、重いロジックの処理を担当。
これらを分離することで、コードの管理がしやすくなり、Cloudflare の強力なエッジネットワークの恩恵をフルに受けられます。
3. 実践的なワークフロー
開発からデプロイまでの流れを整理すると以下のようになります。
-
Worker のデプロイ:
wrangler deployで.workers.devなどのサブドメインに公開される。 -
Route 設定: Cloudflare コンソールで、メインドメインの
/api/*をその Worker に紐付ける。 -
Astro からの呼び出し:
fetch('/api/user')のように相対パスで記述する。
4. 運用上の Tips:環境変数の使い分け
Astro と Worker は別々のプロジェクトとして管理されるため、環境変数も個別に設定する必要があります。
-
Worker 側:
wrangler.tomlまたはコンソールの「Settings」で設定(DB の接続先など)。 -
Astro 側: Pages の「Settings」>「Environment variables」で設定(外部サービスの API キーなど)。
5. 次のステップへの提案
基本的な疎通確認ができたら、次は 「認証」 や 「データの受け渡し」 を強化するのが一般的です。
Astro(クライアント/サーバー)から別体の Worker へデータを送る際、最もシンプルかつ堅牢なのが 「カスタムヘッダーによる API キー認証」 です。
同じドメイン下 (/api/*) で動かしている場合でも、直接 Worker の URL を叩かれるリスクに備えて、以下の実装を行っておくのが安全です。
1. Worker 側の実装(受け取り側)
Worker 側で、リクエストヘッダーに含まれる API キーが、環境変数に設定した値と一致するかをチェックします。
my-api-worker/src/index.ts
TypeScript
export default {
async fetch(request, env, ctx): Promise<Response> {
// 1. ヘッダーから API キーを取得
const apiKey = request.headers.get("X-Custom-API-Key");
// 2. 環境変数の値と照合 (env.API_SECRET_KEY は Cloudflare コンソールで設定)
if (!apiKey || apiKey !== env.API_SECRET_KEY) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "content-type": "application/json" },
});
}
// 3. 認証成功時の処理 (POST リクエストのデータを処理するなど)
if (request.method === "POST") {
const body = await request.json();
return new Response(JSON.stringify({ message: "Data received!", data: body }), {
status: 200,
});
}
return new Response("OK");
},
} satisfies ExportedHandler;
2. Astro 側の実装(送り側)
Astro のサーバーサイド(.astro ファイルのフロントマターや API Endpoints)から Worker を呼び出す際に、ヘッダーにキーを付与します。
my-astro-app/src/pages/submit.astro
コード スニペット
---
// Astro 側の環境変数から API キーを取得
const API_SECRET = import.meta.env.API_SECRET_KEY;
if (Astro.request.method === "POST") {
const formData = await Astro.request.formData();
const name = formData.get("name");
// Worker へのフェッチ (Routes 設定により /api/ で届く)
const response = await fetch(`${Astro.url.origin}/api/data`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Custom-API-Key": API_SECRET, // ここでキーを渡す
},
body: JSON.stringify({ name }),
});
const result = await response.json();
console.log(result);
}
---
<form method="POST">
<input type="text" name="name" placeholder="お名前" />
<button type="submit">送信</button>
</form>
3. Cloudflare コンソールでの設定(重要)
コードに直接キーを書くのは厳禁です。必ず Cloudflare の管理画面から 環境変数 を登録してください。
Worker 側
-
Workers & Pages > my-api-worker > Settings > Variables
-
Environment Variables に
API_SECRET_KEYを追加し、ランダムな文字列を入力して「Encrypt(暗号化)」を有効にして保存。
Astro (Pages) 側
-
Workers & Pages > my-astro-app > Settings > Environment variables
-
Worker と同じ値の
API_SECRET_KEYを追加して保存。
4. この方法の安全性について
-
秘密保持: API キーはサーバーサイド(Astro の SSR または Pages Functions)で付与されるため、ブラウザのデベロッパーツールで見られることはありません。
-
直叩き防止:
/api/*のルートを知っている第三者が直接アクセスしても、正しいヘッダーがない限り Worker は 401 エラーを返します。
5. 次のステップ
さらにセキュリティを高めるなら、Cloudflare Access を使って /api/* へのアクセス自体を特定の IP や認証済みユーザーに絞ることも可能です。
Cloudflare WorkerからCloudflare D1 (SQLデータベース) にデータを書き込むための、最もシンプルで実践的な手順を解説します。
D1を使えば、Astroから送られてきたデータをSQLiteベースの高速なデータベースに保存できます。
1. D1 データベースの作成と初期化
まずは、コマンドライン(Wrangler)を使ってデータベースを作成します。
Bash
# 1. データベースの作成(名前は 'my-db' とします)
npx wrangler d1 create my-db
# 2. 出力された内容(database_idなど)を wrangler.toml に貼り付ける
# [wrangler.toml の例]
# [[d1_databases]]
# binding = "DB"
# database_name = "my-db"
# database_id = "xxxx-xxxx-xxxx-xxxx"
次に、テーブルを作成するためのSQLファイル(例: schema.sql)を用意します。
SQL
-- schema.sql
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
これを実行してテーブルを作成します。
Bash
npx wrangler d1 execute my-db --local --file=./schema.sql # ローカル開発用
npx wrangler d1 execute my-db --remote --file=./schema.sql # 本番環境用
2. Worker 側での書き込み実装
Workerのコード内で、環境変数 env.DB を通じてSQLを実行します。
my-api-worker/src/index.ts
TypeScript
export interface Env {
DB: D1Database; // wrangler.tomlのbinding名と一致させる
API_SECRET_KEY: string;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
// 前回のAPIキー認証(中略)...
const apiKey = request.headers.get("X-Custom-API-Key");
if (apiKey !== env.API_SECRET_KEY) return new Response("Unauthorized", { status: 401 });
if (request.method === "POST") {
try {
const { name } = await request.json();
// D1への書き込み(プリペアドステートメントを使用)
const info = await env.DB.prepare(
"INSERT INTO users (name) VALUES (?)"
)
.bind(name)
.run();
return new Response(JSON.stringify({ success: true, id: info.meta.last_row_id }), {
headers: { "content-type": "application/json" },
});
} catch (e) {
return new Response("Database Error", { status: 500 });
}
}
// データ取得(GET時)の例
const { results } = await env.DB.prepare("SELECT * FROM users ORDER BY created_at DESC LIMIT 10").all();
return Response.json(results);
},
} satisfies ExportedHandler<Env>;
3. Astro 側での呼び出し確認
Astroのサーバーサイドコード(前回作成したもの)から、そのまま name をPOSTすれば、D1にデータが蓄積されます。
4. データの確認方法
デプロイした後、実際にデータが入っているかどうかはダッシュボードまたはコマンドで確認できます。
-
コンソール: Cloudflareダッシュボード > Workers & Pages > D1 > my-db > Tables
-
コマンド: ```bash
npx wrangler d1 execute my-db –remote –command=“SELECT * FROM users;”
5. まとめ:全体の連携フロー
-
Astro: ユーザー入力を受け取り、APIキーを添えて
/api/dataへPOST。 -
Routes: Cloudflareがパスを検知し、リクエストを Worker へルーティング。
-
Worker: APIキーを検証し、問題なければ D1 に
INSERT。 -
D1: データを安全に保存。
これで、フロントエンド(Astro)からバックエンド(Worker)、データベース(D1)までのフルスタックな構成が完了しました!
保存したデータをAstroのページ上に一覧表示する方法を解説します。
この構成では、**Astroのサーバーサイド(SSR)**でWorker(API)を叩き、取得したデータをHTMLとしてレンダリングします。こうすることで、ブラウザがページを読み込んだ瞬間にはすでにデータが表示されている状態(SEOにも強い構成)になります。
1. Worker側:データ取得用エンドポイントの準備
まず、WorkerがD1からデータを全件取得して返すようにします。
my-api-worker/src/index.ts
TypeScript
// GETリクエストが来たらデータを返す処理を追加
if (request.method === "GET") {
try {
const { results } = await env.DB.prepare(
"SELECT id, name, created_at FROM users ORDER BY created_at DESC"
).all();
return new Response(JSON.stringify(results), {
headers: {
"content-type": "application/json",
"Cache-Control": "public, max-age=60" // 任意:1分間キャッシュ
},
});
} catch (e) {
return new Response("Database Error", { status: 500 });
}
}
2. Astro側:データを取得して表示する
Astroのコンポーネントの「フロントマター(---の間)」で、Workerからデータをフェッチします。
src/pages/users.astro
コード スニペット
---
// 1. 環境変数からAPIキーを取得
const API_SECRET = import.meta.env.API_SECRET_KEY;
// 2. Worker(API)からデータを取得
// 同一ドメイン運用なので Astro.url.origin を使って絶対URLを作る
const response = await fetch(`${Astro.url.origin}/api/data`, {
method: "GET",
headers: {
"X-Custom-API-Key": API_SECRET,
},
});
// 型定義(TypeScriptを使う場合)
interface User {
id: number;
name: string;
created_at: string;
}
const users: User[] = await response.json();
---
<html>
<head>
<title>ユーザー一覧</title>
</head>
<body>
<h1>登録ユーザー一覧</h1>
{users.length === 0 && <p>ユーザーはまだ登録されていません。</p>}
<ul>
{users.map((user) => (
<li>
<strong>{user.name}</strong>
<small>(登録日: {new Date(user.created_at).toLocaleString('ja-JP')})</small>
</li>
))}
</ul>
<a href="/">登録画面へ戻る</a>
</body>
</html>
3. この仕組みのポイント
-
秘密の通信: APIキー (
X-Custom-API-Key) はAstroのサーバーサイドで付与されます。ユーザーのブラウザにはAPIキーは一切漏れません。 -
高速な表示: クライアントサイド(JavaScript)で
fetchするのではなく、AstroがHTMLを生成するタイミングでデータを埋め込むため、ページを開いた瞬間にリストが表示されます。 -
同一ドメインの利点:
fetch('/api/data')と書くだけで、Cloudflareのルーティングによって自動的にWorkerへ接続されます。
4. 開発時の注意点
ローカル環境でテストする場合、AstroとWorkerの両方を起動しておく必要があります。
-
Workerを起動:
npx wrangler dev --port 8787 -
Astroを起動:
npx wrangler pages dev ./dist --proxy 8787
これで、localhost:8888/users にアクセスすると、ローカルのD1データが表示されるようになります。
次のステップへのアイデア
-
削除機能: 各リストの横に「削除」ボタンを置き、Workerへ
DELETEリクエストを送る。 -
リアルタイム更新: 登録後すぐに一覧を更新したい場合は、Astroのアイランド(ReactやVue、またはVanilla JS)を使ってクライアントサイドで再取得する。
「特定のユーザーだけを検索・表示する機能」や「ページネーション(件数制限)」の実装についても解説が必要ですか?