zudo-paper

NetlifyからCloudflareへの移行メモ

Author: Takazudo | 作成: 2026/03/08

概要

個人ブログプロジェクト(Next.js静的エクスポート + Docusaurusドキュメントサイト)をNetlifyからCloudflare Pagesに移行した。サーバーレス関数もNetlify FunctionsからPages Functions(Cloudflare Workers)に書き換え、データストレージもNetlify BlobsからCloudflare KVに移した。そのまとめ。

移行の動機

GitHub Organizationを作って、リポジトリをorgに移したところ、Netlifyでorgのプライベートリポジトリを使うにはTeamプラン(有料)が必要だと言われた。個人のプライベートリポジトリなら無料プランで使えていたのだが、orgになると話が変わる。

Cloudflare Pagesは無料プランでもorgのプライベートリポジトリに対応している。加えて、無料プランでも帯域制限なし、ビルド回数500回/月と太っ腹。KV、R2(オブジェクトストレージ)、D1(SQLite)などエッジストレージの選択肢も豊富で、Netlifyよりもインフラの拡張性がある。

Netlify自体は良いサービスで、使い勝手も悪くない。ただ、自分のケースではCloudflareの方が合っていた。

Cloudflare Pages の概要

Cloudflare Pagesは静的サイトホスティングサービスで、Netlify・Vercelと同カテゴリ。Cloudflareのグローバルエッジネットワーク上で動作する。

デプロイ方法はいくつかあるが、GitHub Actions + wrangler CLIでデプロイする方式がおすすめ。Cloudflare側のビルド機能を使うこともできるが、GitHub Actionsで自分でビルドしてからデプロイする方が柔軟性がある。

Pages Functions

Cloudflare Pagesに統合されたサーバーレス関数で、実体はCloudflare Workers。ファイルベースルーティングで、functions/api/search.tsを置くと/api/searchでアクセスできる。

wrangler pages deploy時に自動的にesbuildでバンドル・コンパイルされる。

Netlify Functionsとの違いとして、関数のシグネチャが異なる。Netlify Functionsはexport default async (req, context) => {...}という形式だが、Pages Functionsは以下のようになる。

interface Env {
  KEYWORD_LOGS: KVNamespace;
}
 
export const onRequestGet: PagesFunction<Env> = async (context) => {
  const url = new URL(context.request.url);
  const query = url.searchParams.get("q") || "";
 
  // Cloudflare KV へのアクセス
  const kv = context.env.KEYWORD_LOGS;
  await kv.put("key", JSON.stringify({ data: "value" }));
 
  return new Response(JSON.stringify({ results: [] }), {
    headers: { "Content-Type": "application/json" },
  });
};

EnvインターフェースでKVなどのバインディングを型定義し、context.env経由でアクセスする。onRequestGetはGETリクエスト用のハンドラで、onRequestPostにすればPOSTになる。

Netlify Blobs → Cloudflare KV

データストレージはNetlify BlobsからCloudflare KVに移行した。KVネームスペースはwrangler kv namespace create KEYWORD_LOGSで作成できる。

wrangler.tomlにバインディングを設定する。

name = "my-project"
compatibility_date = "2024-12-01"
 
[[kv_namespaces]]
binding = "KEYWORD_LOGS"
id = "your-namespace-id"

KVのAPIはシンプルで、putgetだけ覚えれば使える。Netlify Blobsも似たようなAPIだったので、移行はほぼ機械的な書き換えだった。

GitHub Actions からのデプロイ

GitHub Actionsのワークフローでビルドしてからwrangler pages deployでデプロイする。

- name: Install function dependencies
  run: npm install minisearch
 
- name: Deploy to Cloudflare Pages
  run: |
    wrangler pages deploy deploy \
      --project-name=my-project \
      --branch=main \
      --commit-hash=${GITHUB_SHA}
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_IDをGitHubのSecretsに登録しておく。

PRプレビューも簡単で、--branch="pr-${PR_NUMBER}"を指定するとhttps://pr-123.my-project.pages.devのようなプレビューURLが生成される。

Netlify との比較

項目NetlifyCloudflare Pages
サーバーレス関数Netlify FunctionsPages Functions
関数の形式export default async (req, context) => {...}export const onRequestGet: PagesFunction<Env> = async (context) => {...}
データストレージNetlify BlobsCloudflare KV
デプロイCLInetlify deploywrangler pages deploy
ルーティング設定netlify.tomlのredirects_redirectsファイル

概念的には対応関係が明確なので、移行作業自体はそこまで大きくない。関数のシグネチャを書き換えて、ストレージAPIを差し替えて、デプロイ設定を変更する、という流れ。

functions ディレクトリの配置についての注意点

wrangler pages deploy <output-dir>functions/ディレクトリを現在のワーキングディレクトリから探す。<output-dir>/functions/に置いてしまうと、静的ファイルとして扱われて関数がコンパイルされない。

# NG: functions を deploy ディレクトリの中に置く
deploy/
  functions/    ← 静的ファイルとして扱われる
  index.html
 
# OK: functions を deploy ディレクトリの兄弟に置く
functions/      ← wrangler が自動検出してコンパイル
deploy/
  index.html

正しく配置できていれば、wranglerのログにCompiled Worker successfullyと表示される。これが出ていなければ関数が検出されていない。自分はここで配置を間違えて、関数がデプロイされていないのにしばらく気づかなかった。デプロイログを確認するのが確実。

まとめ

移行自体は関数の書き換えとデプロイ設定の変更で完了し、大きな困難はなかった。Cloudflareは無料プランが充実していて、orgのプライベートリポジトリも対応しているので、自分のケースには合っていた。functionsディレクトリの配置だけ注意。

余談

Cloudflareはエッジストレージの選択肢が豊富で、KV以外にもR2(S3互換オブジェクトストレージ、エグレス料金なし)やD1(SQLite)がある。今回はKVしか使っていないが、将来的にもう少し複雑なデータを扱う場面が出てきたらD1を試してみるのも良さそう。