Next.js のホスティング先といえば Vercel が定番だが、個人開発で使い込むと無料枠の制限が気になってくる。帯域制限、ビルド時間、チームメンバー数……。趣味プロジェクトでも「いつ課金が発生するか」を気にしながら開発するのは地味にストレスがある。
Cloudflare Pages なら、帯域無制限・カスタムドメイン無料SSL付き・プレビューデプロイ付きで、個人開発の規模なら完全に無料枠内で運用できる。
実際に、Next.js で作った Web ツール集「ぱんだツールズ」(69ツール搭載)を Cloudflare Pages で運用している。この記事では、その構成・設定・ハマりどころを実体験ベースで解説する。
Cloudflare Pages の無料枠でできること
まず、Cloudflare Pages の無料プラン(Free)で何ができるかを整理する。
| 項目 | Cloudflare Pages(Free) | Vercel(Hobby) |
|---|---|---|
| ビルド回数 | 500回/月 | 週100時間(ビルド時間) |
| 帯域 | 無制限 | 100GB/月 |
| リクエスト数 | 無制限 | — |
| サイト数 | 100サイト | — |
| カスタムドメイン | 無料SSL付き | 無料SSL付き |
| プレビューデプロイ | ブランチごとに自動 | ブランチごとに自動 |
| 商用利用 | 可 | 非商用のみ |
一番大きいのは 帯域無制限 と 商用利用可 の2点。
Vercel の Hobby プランは帯域100GB/月の制限があり、商用利用は Pro プラン($20/月〜)が必要になる。個人開発でも AdSense を入れたり、将来的に課金機能を付けたりする予定があるなら、Cloudflare Pages のほうが安心して使える。
月500ビルドという制限も、個人開発なら十分すぎる。1日10回デプロイしても1ヶ月で300回。そうそう超えることはない。
Next.js × Cloudflare Pages の構成パターン
Next.js を Cloudflare Pages で動かす方法は大きく2つある。
Static Export(output: 'export')
Next.js のビルド時にすべてのページを HTML として書き出す方式。Cloudflare Pages には静的ファイルだけがデプロイされる。
- サーバーサイドの処理が一切不要
- API Routes、Middleware、ISR は使えない
- CDN から直接配信されるのでレスポンスが速い
- ぱんだツールズはこの方式
@cloudflare/next-on-pages(SSR対応)
Cloudflare Workers 上で Next.js の SSR を動かす方式。@cloudflare/next-on-pages パッケージを使う。
- API Routes、Server Components の動的レンダリングが使える
- Cloudflare Workers の制限(CPU時間、メモリ)に注意が必要
- 設定がやや複雑
どちらを選ぶかの判断基準
結論から言えば、サーバーサイドの処理が不要なら Static Export 一択。
ぱんだツールズのようにブラウザ内で処理が完結するサイト、ブログ、ポートフォリオ、ドキュメントサイトなら Static Export で十分。設定がシンプルで、ハマりどころも少ない。
認証機能がある、データベースにアクセスする、動的に OGP 画像を生成する、といったケースでは @cloudflare/next-on-pages が必要になる。ただし、その場合は Vercel のほうがエコシステムが整っている面もあるので、移行のメリットとデメリットを天秤にかけたほうがいい。
Static Export での構築手順
ここからは、ぱんだツールズで実際に使っている構成を元に、Static Export × Cloudflare Pages のセットアップ手順を解説する。
next.config の設定
next.config.ts で output: 'export' と images.unoptimized: true を指定する。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: 'export',
images: {
unoptimized: true,
},
};
export default nextConfig;
output: 'export' を指定すると、next build 実行時に out/ ディレクトリに静的ファイルが出力される。Cloudflare Pages にはこの out/ をデプロイする。
images.unoptimized: true は後述するが、Static Export では Next.js の画像最適化サーバーが動かないため必須の設定。
Cloudflare Pages のプロジェクト作成
Cloudflare のダッシュボードから設定する。
- Cloudflare ダッシュボード → Pages → 「Create a project」
- 「Connect to Git」で GitHub リポジトリを連携
- ビルド設定を入力:
Framework preset: Next.js (Static HTML Export)
Build command: npm run build
Build output directory: out
これだけでデプロイが動く。main ブランチに push するたびに自動でビルド → デプロイが走る。
カスタムドメインの設定
Cloudflare Pages のプロジェクト設定 → 「Custom domains」からドメインを追加する。DNS が Cloudflare で管理されていれば、数分で SSL 証明書の発行まで自動で完了する。
ぱんだツールズでは sakutto-panda.com を設定している。SSL 証明書の管理や更新を一切気にしなくていいのは、地味だがかなり楽。
環境変数の設定
Cloudflare Pages のプロジェクト設定 → 「Environment variables」で設定する。
NEXT_PUBLIC_GA_ID = G-XXXXXXXXXX
NEXT_PUBLIC_ADSENSE_ID = ca-pub-XXXXXXXXXXXXXXXX
Static Export では ビルド時にしか環境変数を参照できない 点に注意。NEXT_PUBLIC_ プレフィックス付きの変数はビルド時にバンドルに埋め込まれるので問題ないが、サーバーサイドでランタイムに参照する process.env.SECRET_KEY のような使い方はできない。
ハマりどころと対策
next/image が使えない問題
Static Export で最初にぶつかる壁がこれ。
Next.js の <Image> コンポーネントは、デフォルトでサーバーサイドの画像最適化(リサイズ・WebP変換・キャッシュ)を使う。Static Export ではサーバーが存在しないので、そのままだとビルドエラーになる。
対策は next.config.ts で images.unoptimized: true を設定すること。
const nextConfig: NextConfig = {
output: 'export',
images: {
unoptimized: true, // 画像最適化を無効化
},
};
この設定で <Image> コンポーネント自体は使えるようになるが、画像最適化は効かなくなる。width / height / loading="lazy" による遅延読み込みは引き続き機能するので、レイアウトシフト防止の恩恵は受けられる。
画像の最適化が必要な場合は、ビルド前に自分で WebP 変換やリサイズをしておくか、Cloudflare Images(別サービス、有料)を使うことになる。
ルーティングの設計
Static Export では、ビルド時に存在するページだけが生成される。動的にページを追加することはできない。
ぱんだツールズでは、ツールごとにディレクトリを切る構成にしている。
src/app/tools/
pdf-compress/page.tsx # /tools/pdf-compress
image-resize/page.tsx # /tools/image-resize
csv-encoding/page.tsx # /tools/csv-encoding
...(69ツール分)
Dynamic Routes([slug])+ generateStaticParams を使う方法もあるが、ぱんだツールズではツールごとに UI が異なるため、各ディレクトリに page.tsx を配置する方式を採用している。Static Export ではどちらの方式でもビルド時にすべてのページが HTML として書き出される。
Static Export では src/app/not-found.tsx を作ると out/404.html として出力される。Cloudflare Pages はこの 404.html を自動で認識する。
sitemap の生成
Static Export でも sitemap.ts は使える。export const dynamic = 'force-static' を指定すれば、ビルド時に sitemap.xml が生成される。
import { MetadataRoute } from 'next'
export const dynamic = 'force-static'
const BASE_URL = 'https://sakutto-panda.com'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: BASE_URL,
lastModified: new Date('2026-04-11'),
changeFrequency: 'weekly',
priority: 1.0,
},
// 各ツールページをハードコードで列挙する方法もあるが...
]
}
実際のぱんだツールズでは、ツールデータを一元管理しているモジュールから動的に生成している。
import { MetadataRoute } from 'next'
import { tools } from '@/data/tools'
export const dynamic = 'force-static'
const BASE_URL = 'https://sakutto-panda.com'
export default function sitemap(): MetadataRoute.Sitemap {
const toolPages = tools.map((tool) => ({
url: `${BASE_URL}/tools/${tool.slug}`,
lastModified: new Date(tool.updatedAt),
changeFrequency: 'monthly' as const,
priority: 0.8,
}))
return [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1.0,
},
...toolPages,
]
}
ツールを追加するたびに sitemap を手動で更新する必要がなくなる。ツール数が69もあると、ハードコードでの管理は現実的でない。SEO を意識するなら sitemap は必須。Static Export でも問題なく動作する。
ビルドサイズと時間
ツール数が増えるとビルド時間も伸びる。ぱんだツールズは69ツール(カテゴリページ含め77ページ以上)あるが、ビルド時間は Cloudflare Pages 上で2〜3分程度。無料枠の月500ビルドの範囲で十分収まる。
バンドルサイズが気になる場合は、@next/bundle-analyzer で可視化するのが有効。ぱんだツールズでも導入している。
import bundleAnalyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});
export default withBundleAnalyzer(nextConfig);
ANALYZE=true npm run build
各ツールの処理ライブラリ(pdf-lib、SheetJS など)は該当ページでしか読み込まれないので、動的インポートやページ単位のコード分割が効いていればバンドルサイズは問題になりにくい。
実運用 Tips
プレビューデプロイの活用
Cloudflare Pages は main 以外のブランチに push すると、自動でプレビューデプロイが走る。URL は <branch-name>.<project-name>.pages.dev の形式。
本番にデプロイする前に実際の動作確認ができるので、PR レビュー時に「このURLで確認してください」と共有できて便利。
Cloudflare Web Analytics
Cloudflare Pages を使っているなら、Cloudflare Web Analytics も無料で使える。ダッシュボードの「Web Analytics」からドメインを追加するだけで設定完了。
Google Analytics と併用もできる。Cloudflare Web Analytics は JavaScript ビーコン方式(クライアントサイド)で計測するが、計測スクリプトがファーストパーティドメインから配信されるため、サードパーティスクリプトをブロックする広告ブロッカーの対象になりにくいという利点がある。
キャッシュ戦略
Static Export の場合、すべてのファイルが静的なので CDN キャッシュが非常に効く。Cloudflare のデフォルトキャッシュ設定でも十分なパフォーマンスが出る。
追加でキャッシュヘッダーを設定したい場合は、public/_headers ファイルを作成する。
/assets/*
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: public, max-age=0, must-revalidate
Next.js の Static Export では _next/static/ 配下にハッシュ付きのアセットが出力されるので、これらには長めのキャッシュを設定して問題ない。
_redirects ファイル
リダイレクトが必要な場合は public/_redirects ファイルを使う。
/old-path /new-path 301
Cloudflare Pages はこのファイルを自動で認識してリダイレクトを処理してくれる。
Vercel から移行する場合の注意点
既に Vercel でホスティングしている Next.js サイトを Cloudflare Pages に移行するケースも増えている。移行時に注意すべき点をまとめる。
使えなくなる機能
Static Export に切り替える場合、以下の機能は使えなくなる。
- ISR(Incremental Static Regeneration): ビルド時にすべてのページを生成する必要がある
- API Routes: サーバーサイドの処理が必要なら別途 Cloudflare Workers や外部 API を用意する
- Middleware: 認証チェックやリダイレクトを Middleware でやっていた場合、別の手段に置き換える必要がある
- Server Actions: フォーム送信などサーバー処理を使っていた場合は対応不可
-
next/image の最適化:
unoptimized: trueが必須
移行手順の概要
-
next.configにoutput: 'export'とimages.unoptimized: trueを追加 -
next buildを実行してエラーを確認。サーバーサイド機能を使っている箇所を洗い出す - API Routes → 外部 API or Cloudflare Workers に移行
- Middleware →
_redirectsファイル or Cloudflare のルール設定に移行 - ISR → Static Export に変更(
generateStaticParamsで全ページをビルド時生成) - ビルドが通ったら Cloudflare Pages にデプロイ
- DNS をCloudflareに向ける or カスタムドメインを設定
移行してよかったこと
- 帯域制限を気にしなくてよくなった
- 商用利用の制限がなくなった
- SSL やCDN の設定を Cloudflare に一元化できた
- Cloudflare のセキュリティ機能(DDoS対策、WAF)が自動で適用される
困ったこと
-
next/imageの最適化が使えないので、画像が多いサイトではビルド前の手動最適化が必要 - Vercel に比べてビルドログが見づらい(慣れの問題)
- Next.js の新機能が Vercel 前提で発表されることが多く、Cloudflare Pages での対応状況を毎回確認する必要がある
まとめ
Cloudflare Pages × Next.js Static Export は、個人開発のホスティング先として非常にバランスがいい組み合わせ。
- 帯域無制限・商用利用可の無料枠
-
output: 'export'とimages.unoptimized: trueの2行でデプロイ可能 - ISR や API Routes は使えないが、ブラウザ完結のサイトなら問題にならない
- プレビューデプロイ、無料SSL、Cloudflare のセキュリティ機能が付いてくる
ぱんだツールズは69ツール・77ページ以上を載せた状態でも、この構成で問題なく運用できている。Vercel の料金や制限が気になっている人は、一度試してみる価値はある。
ぱんだツールズは、PDF・画像・CSV・テキスト処理など69種類のツールをすべて無料・ブラウザ完結で提供している。ファイルをサーバーに送らないので、業務データも安心して処理できる。
この記事は Zenn にも同じ内容を投稿しています。