0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare Pages + Next.js — Vercelとどっちがいいかを比べた

0
Posted at

はじめに

Next.jsのデプロイ先としてVercelを使っていたが、Cloudflare PagesがNext.jsに対応していると知って試してみた。

コスト・機能・制約の違いを実際に使って比べた。「Vercelで十分じゃないの?」という状態から始まって、どんな場面でCloudflare Pagesを選ぶかの判断基準が固まってきた。


Cloudflare PagesとVercelの概要

Vercel:
  Next.jsを作ったVercel社のホスティング
  Next.jsの全機能をサポート
  自動プレビューデプロイ
  Edge Functionsも対応

Cloudflare Pages:
  Cloudflareのフロントエンドホスティング
  Workers統合でエッジ処理が得意
  無料枠が充実
  Next.jsはサードパーティ対応(@cloudflare/next-on-pages)

料金比較

Vercel

Hobby(無料):
  帯域幅: 100GB/月
  関数の実行: 100GB-hours/月
  ビルド時間: 6,000分/月
  商用利用: 禁止

Pro($20/月):
  帯域幅: 1TB/月
  関数の実行: 1,000GB-hours/月
  チームメンバー: 無制限
  商用利用: 可能

Cloudflare Pages

Free(無料):
  帯域幅: 無制限
  リクエスト: 無制限
  ビルド: 500回/月
  商用利用: 可能

Pro($20/月):
  ビルド: 5,000回/月
  早いビルド
  追加機能

無料枠の帯域幅が無制限というのがCloudflare Pagesの最大の強み。Vercelの無料枠は商用利用禁止なので、個人の商用プロジェクトはCloudflare Pagesのほうが使いやすい。


Cloudflare PagesへのNext.jsデプロイ

セットアップ

# @cloudflare/next-on-pagesをインストール
npm install -D @cloudflare/next-on-pages
// next.config.mjs
import { setupDevPlatform } from "@cloudflare/next-on-pages/next-dev";

/** @type {import('next').NextConfig} */
const nextConfig = {
    // Cloudflare Pages用の設定
};

// 開発環境でWorkers bindingsを使う
if (process.env.NODE_ENV === "development") {
    await setupDevPlatform();
}

export default nextConfig;
# wrangler.toml
name                   = "my-next-app"
compatibility_date     = "2024-01-01"
compatibility_flags    = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"

[[kv_namespaces]]
binding = "MY_KV"
id      = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

[[d1_databases]]
binding      = "MY_DB"
database_name = "my-database"
database_id  = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
// package.json
{
    "scripts": {
        "build":         "next build",
        "pages:build":   "npx @cloudflare/next-on-pages",
        "pages:deploy":  "wrangler pages deploy",
        "pages:preview": "wrangler pages dev",
        "preview":       "npm run pages:build && npm run pages:preview"
    }
}

Edge Runtimeへの対応

Cloudflare PagesではすべてのサーバーサイドコードがEdge Runtimeで動く。これが一番大きな制約。

// ✓ Edge Runtimeで動くRoute Handler
// app/api/users/route.ts
export const runtime = "edge";

export async function GET(request: Request) {
    // EdgeランタイムではNode.js APIが使えない
    // fetch, URL, Request, Response, Headers等のWeb APIは使える
    const res  = await fetch("https://api.example.com/users");
    const data = await res.json();
    return Response.json(data);
}
// ✗ Edgeランタイムでは使えないもの
// import fs from "fs"         → ✗ ファイルシステム不可
// import crypto from "crypto" → △ Web Crypto APIは使える
// import { Pool } from "pg"   → ✗ Node.jsネイティブモジュール不可
// prisma / drizzle            → △ Edgeサポートがあるものは動く

Cloudflare PagesでDBアクセスする場合

// app/api/users/route.ts
// D1(SQLite)はEdgeで使える
import { getRequestContext } from "@cloudflare/next-on-pages";

export const runtime = "edge";

export async function GET() {
    const { env } = getRequestContext();
    const db      = env.MY_DB;

    const result = await db.prepare(
        "SELECT * FROM users ORDER BY created_at DESC LIMIT 20"
    ).all();

    return Response.json(result.results);
}

export async function POST(request: Request) {
    const { env }             = getRequestContext();
    const { name, email }     = await request.json() as { name: string; email: string };
    const db                  = env.MY_DB;

    const user = await db.prepare(
        "INSERT INTO users (name, email) VALUES (?, ?) RETURNING *"
    ).bind(name, email).first();

    return Response.json(user, { status: 201 });
}

KVとD1をNext.jsから使う

// lib/cloudflare.ts
// Cloudflare bindingsへのアクセスをラップするユーティリティ

import { getRequestContext } from "@cloudflare/next-on-pages";

export function getCloudflareEnv() {
    return getRequestContext().env;
}

// KVのラッパー
export async function kvGet(key: string): Promise<string | null> {
    const { MY_KV } = getCloudflareEnv();
    return MY_KV.get(key);
}

export async function kvSet(
    key:   string,
    value: string,
    ttl?:  number,
): Promise<void> {
    const { MY_KV } = getCloudflareEnv();
    await MY_KV.put(key, value, ttl ? { expirationTtl: ttl } : undefined);
}
// app/api/session/route.ts
import { getCloudflareEnv, kvGet, kvSet } from "@/lib/cloudflare";

export const runtime = "edge";

export async function GET(request: Request) {
    const sessionId = new URL(request.url).searchParams.get("id");
    if (!sessionId) return Response.json({ error: "Session ID required" }, { status: 400 });

    const session = await kvGet(`session:${sessionId}`);
    if (!session)  return Response.json({ error: "Session not found" }, { status: 404 });

    return Response.json(JSON.parse(session));
}

VercelとCloudflare Pagesの機能比較

機能 Vercel Cloudflare Pages
Next.jsサポート ◎ 公式 △ サードパーティ
Node.js API ◎ フル対応 ✗ Edgeのみ
プレビューデプロイ ◎ 自動 ◎ 自動
帯域幅 100GB/月(無料) 無制限(無料)
商用利用(無料) ✗ 禁止 ✓ 可能
Workers連携 △ ◎ シームレス
KV/D1/R2 ✗ ◎
ビルド時間 速い やや遅い
カスタムドメイン 無料 無料
ログ・分析 ◎ ○

どちらを選ぶか

Vercelを選ぶとき:
  ✓ Next.jsの全機能を使いたい(特にServer Actionsの高度な機能)
  ✓ Node.js固有のライブラリを使う(prisma, sharp等)
  ✓ チーム開発でプレビューの使い勝手を重視
  ✓ 個人ホビープロジェクト(商用でない)

Cloudflare Pagesを選ぶとき:
  ✓ 無料で商用利用したい
  ✓ Workers/KV/D1/R2との組み合わせで使う
  ✓ 帯域幅が多くなりそう
  ✓ Cloudflareで既にDNSを管理している
  ✓ グローバルに低レイテンシが必要
  ✓ Next.jsをEdge Runtimeのみで完結させられる

移行時の注意点

VercelからCloudflare Pagesに移行するときに詰まったポイント。

① Node.jsモジュールが使えない

// Vercelでは動くがCloudflare Pagesでは動かない例

// ✗ fsモジュール
import fs from "fs";
const file = fs.readFileSync("./data.json");

// ✓ 代替: publicフォルダに置いてfetchで読む
const res  = await fetch("/data.json");
const data = await res.json();

② Prismaの対応

// Prismaはデフォルトではエッジ非対応
// @prisma/adapter-d1 を使うとD1で動く

import { PrismaClient } from "@prisma/client";
import { PrismaD1 }     from "@prisma/adapter-d1";

export const runtime = "edge";

export async function GET() {
    const { env }    = getRequestContext();
    const adapter    = new PrismaD1(env.MY_DB);
    const prisma     = new PrismaClient({ adapter });
    const users      = await prisma.user.findMany();
    return Response.json(users);
}

③ ImageResponseが動かない場合

// OG画像生成(@vercel/og)は一部制約がある
// Cloudflare Pagesでは動作確認が必要
export const runtime = "edge";

import { ImageResponse } from "next/og";

export async function GET() {
    return new ImageResponse(
        <div style={{ display: "flex" }}>Hello OG</div>,
        { width: 1200, height: 630 }
    );
}

GitHub ActionsでCloudflare Pagesにデプロイ

# .github/workflows/pages-deploy.yml
name: Deploy to Cloudflare Pages

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      deployments: write

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build Next.js
        run: npm run build
        env:
          NODE_ENV: production

      - name: Build for Cloudflare Pages
        run: npx @cloudflare/next-on-pages

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken:         ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId:        ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command:          pages deploy .vercel/output/static --project-name=my-next-app
          gitHubToken:      ${{ secrets.GITHUB_TOKEN }}

Cloudflare PagesとWorkersを組み合わせる

典型的な構成:

[ブラウザ]
    ↓
[Cloudflare Pages(Next.js)] ← フロントエンド
    ↓ API呼び出し
[Cloudflare Workers(Hono)]   ← APIレイヤー
    ↓
[D1 / R2 / KV / 外部API]
// Next.jsからWorkersを呼ぶ
// app/api/data/route.ts
export const runtime = "edge";

export async function GET(request: Request) {
    // 同一Cloudflareアカウントの別WorkerをService Bindingで呼べる
    const { env } = getRequestContext();

    // Service Bindingがあれば内部ネットワークで呼べる(外部への通信なし)
    // const result = await env.MY_WORKER.fetch(request);

    // 通常のfetchでも可
    const res  = await fetch("https://my-worker.my-subdomain.workers.dev/data");
    const data = await res.json();
    return Response.json(data);
}

まとめ

  • Cloudflare Pagesは無料で商用利用可能かつ帯域幅無制限が最大の強み
  • Node.js APIが使えないEdgeの制約がVercelとの最大の違い
  • Workers/KV/D1/R2との統合はCloudflare Pagesが自然
  • Next.jsの全機能(特にServer Components + Node.js API)が必要ならVercel
  • Cloudflareで既にDNS管理しているなら設定がシンプル

個人的な使い分けとして「Node.jsライブラリが必要または複雑なServer Actions → Vercel」「シンプルなNext.js + Cloudflareのエコシステムを使いたい → Cloudflare Pages」という基準に落ち着いた。


参考


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?