💡 Tips

Cloudflare Pages FunctionsでAPIルートを追加する実装手順【個人開発・2025年版】

結論:静的サイトの機能追加はPages Functionsのfunctions/以下にファイルを置くだけで実現できる

AstroやNext.jsの静的書き出しサイトに「フォーム送信」「外部APIの認証プロキシ」「簡易的な集計API」を足したいとき、わざわざ別途サーバーやCloudflare Workersプロジェクトを新設する必要はない。

Cloudflare PagesにはPages Functionsという仕組みがあり、リポジトリ内にfunctions/ディレクトリを作ってファイルを置くだけで、そのファイルパスがそのままAPIルートになる。追加のデプロイ設定もインフラ構築も不要で、静的サイトのビルド・デプロイフローに乗ったまま裏側にサーバーレス処理を足せる。

個人開発で「ちょっとしたAPIエンドポイントだけ欲しい」という場面には、これが最も手数が少ない選択肢だ。以下、具体的な手順を追って説明する。

Pages FunctionsとCloudflare Workersの違い

最初に混同しやすいポイントを整理する。

項目Pages FunctionsCloudflare Workers(単体)
デプロイ単位静的サイトと同一プロジェクト独立したプロジェクト
ルーティングファイルパス=URLパス(規約ベース)wrangler.tomlで手動設定
向いている規模静的サイトに付随する小〜中規模APIAPI単体・複数サービス連携
実行環境Workers runtime(同一)Workers runtime
料金Workers Free/Paidプランに準拠同左

実行環境自体はどちらもWorkers runtimeなので性能差はない。違いは「デプロイの単位とルーティングの決め方」だけだと理解しておくとよい。既存の静的サイトに軽く機能を足したいなら Pages Functions、APIを本格的に育てて複数サイトから使い回すならWorkers単体、という使い分けになる。

料金体系は変更されることがあるため、契約前に必ず公式の最新情報を確認してほしい。

実装手順

1. functionsディレクトリを作る

プロジェクトルート直下(静的サイトのビルド出力とは別)にfunctions/を作成する。Astroであればsrc/とは別に、リポジトリのトップレベルに置く。

my-site/
├── src/
├── functions/
│   └── api/
│       └── contact.ts
└── wrangler.toml

2. APIルートを実装する

functions/api/contact.tsに置いたファイルは、そのまま/api/contactというURLでアクセスできるようになる。

export const onRequestPost: PagesFunction = async (context) => {
  const { request, env } = context;
  const body = await request.json();

  if (!body.email || !body.message) {
    return new Response(JSON.stringify({ error: "invalid params" }), {
      status: 400,
      headers: { "Content-Type": "application/json" },
    });
  }

  // 例: Slack Webhookへ通知を転送する
  await fetch(env.SLACK_WEBHOOK_URL, {
    method: "POST",
    body: JSON.stringify({ text: `お問い合わせ: ${body.email} - ${body.message}` }),
  });

  return new Response(JSON.stringify({ ok: true }), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
};

onRequestGet / onRequestPostのようにHTTPメソッドごとに関数をエクスポートするのが規約。onRequest(メソッド無指定)を使えば全メソッドをまとめて受けることもできる。

3. 動的ルートを扱う

ファイル名を[id].tsのように角括弧にすると動的パラメータとして扱える。

functions/api/users/[id].ts  →  /api/users/123 にマッチ
export const onRequestGet: PagesFunction = async (context) => {
  const id = context.params.id;
  return new Response(JSON.stringify({ userId: id }));
};

4. 環境変数・シークレットを設定する

Slack Webhook URLやAPIキーなどの秘匿情報は、コードに直書きせずCloudflareダッシュボードの「Settings > Environment variables」またはCLIから設定する。

wrangler pages secret put SLACK_WEBHOOK_URL --project-name my-site

ローカル開発では.dev.varsファイルに同名の変数を書いておけば、wrangler pages dev実行時に読み込まれる。このファイルは.gitignoreに必ず入れておくこと。

5. ミドルウェアで共通処理をまとめる

認証チェックやCORS設定など複数ルートで共通する処理は、functions/_middleware.tsに書くとディレクトリ配下すべてに適用される。

export const onRequest: PagesFunction = async (context) => {
  const response = await context.next();
  response.headers.set("Access-Control-Allow-Origin", "*");
  return response;
};

6. ローカル検証してからデプロイする

npx wrangler pages dev ./dist

ビルド出力ディレクトリを指定して起動すると、静的ファイルとFunctionsの両方をローカルで動作確認できる。ここで一度動かしてからGitにpushしてデプロイする流れにすると、本番で初めてエラーに気づく事態を避けられる。

つまずきやすいポイントと注意点

  • KVやD1へのバインディングはwrangler.tomlに明示が必要。 バインディング名を打ち間違えるとenv.DBがundefinedになり原因が分かりにくい。DBを使う構成にするならCloudflare D1 × Drizzle ORM マイグレーション運用手順も合わせて確認しておくと設計がぶれない。
  • Node.js組み込みAPIは基本的に使えない。 fsやcryptoのNode版に依存したライブラリはWorkers環境で動かないことが多く、Web標準API(fetch、crypto.subtleなど)で書き直す必要がある。
  • 実行時間・メモリに制限がある。 重い画像処理やバッチ集計のような長時間処理には向かない。そうした処理は別途キューイングする構成のほうが安定する。
  • ルーティング競合に注意。 静的ファイルと同じパスにfunctions/のファイルを置くと、どちらが優先されるか分かりにくくなることがある。API用のパスは/api/配下に統一しておくと事故が減る。

これらはいずれも「サーバーレスだから性能が悪い」という話ではなく、実行環境の制約を把握しないまま設計すると詰まる、という種類の注意点だ。仕様は変更されることがあるため、着手前に公式ドキュメントの最新版を確認してほしい。

まとめ

Cloudflare Pages Functionsは、既存の静的サイトに手数少なくサーバーレスAPIを足せる選択肢だ。functions/以下にファイルを置くだけでルーティングが決まり、追加のデプロイ設定もほぼ不要になる。

個人開発でお問い合わせフォームや簡易APIが欲しいだけなら、まずこの構成で十分間に合う。デプロイ手順そのものに不安がある場合はCloudflare PagesにAstroサイトをデプロイする方法を先に確認しておくとスムーズに進められる。