【Next.js × Cloudflare】デプロイしたのにページが更新されない!?2つの原因と自動パージによる完全解決法
公開日: 2026年08月14日
WordPress をヘッドレスCMSとして使い、フロントエンドを Next.js(静的エクスポート / SSG)+ Cloudflare Pages でホスティングするモダンなブログ構成を運用していたところ、以下の問題に直面しました。
😱 「WordPress で新着記事を書いてデプロイしたのに、本番サイト(Cloudflare)を開いてもページが全く更新されない!」 (※
pnpm run devの開発環境ではちゃんと新着記事が表示されているのに…)
調査を進めると、「Next.js 側のビルドキャッシュ」 と 「Cloudflare CDN 側のエッジキャッシュ」 という 2箇所のキャッシュの罠 が原因でした。
今回は、この問題の根本原因の解説と、デプロイ完了時に Cloudflare のキャッシュを全自動でパージ(消去)して即座に最新化する仕組み を備忘録としてまとめます。
🔍 原因 1: Next.js のビルドキャッシュ残り(.next/cache)
発生していた現象
pnpm run dev ではリアルタイムに WordPress から記事を取得できるのに、本番ビルド(next build)を実行した静的 HTML(./out/index.html)に新着記事が含まれていませんでした。
理由
Next.js(App Router)の静的エクスポート(output: 'export')では、fetch API に cache: 'force-cache' を指定して静的生成(SSG)を行う必要があります(※ no-store にすると動的SSR扱いになり NEXT_STATIC_GEN_BAILOUT エラーでビルドが失敗します)。
しかし、force-cache にしていると、Next.js は .next/cache に残っている前回のビルド時の API レスポンスを再利用 してしまい、WordPress の最新データを取得せずに古い HTML を生成してしまっていました。
解決策
src/lib/wordpress.tsで開発時とビルド時でキャッシュ設定を切り替える:const res = await fetch(WP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }), // 開発時は no-store、ビルド時(SSG)は force-cache ...(process.env.NODE_ENV === 'development' ? { cache: 'no-store' } : { cache: 'force-cache' }), });package.jsonのビルドコマンドの直前で.next/cacheを毎回自動削除 する:"scripts": { "build": "rm -rf .next/cache && next build" }
これで、ビルドを実行するたびに必ず WordPress から最新記事を取得して HTML が生成されるようになりました。
🔍 原因 2: Cloudflare CDN のエッジキャッシュ(cf-cache-status: HIT)
発生していた現象
ビルドされた HTML には新着記事が入っているのに、公開ドメイン(https://blog.example.com)にアクセスしても古いページが表示される。Cloudflare 管理画面で手動パージすると表示される。
理由
レスポンスヘッダーを curl -I https://blog.example.com で確認したところ、Cloudflare の CDN エッジサーバーが HTML をキャッシュ(cf-cache-status: HIT)して返していました。
HTTP/2 200
server: cloudflare
age: 697
cf-cache-status: HIT ← Cloudflare CDN が古い HTML を保持している
カスタムドメインを Cloudflare のプロキシ(Proxy ☁️)経由で配信している場合、デプロイ完了後も CDN のエッジキャッシュが残ってしまいます。
💡 解決策: デプロイ完了時に自動でキャッシュパージする仕組みを作る
「デプロイのたびに Cloudflare 管理画面を開いて手動パージする」のは面倒ですし、運用の事故に繋がります。 そこで、「ビルド ➔ Pages デプロイ ➔ Cloudflare API でキャッシュ自動パージ」までを 1 つのコマンドで完結 させるスクリプトを作成しました。
ステップ 1: Cloudflare で API トークンを発行する
- Cloudflare ダッシュボード ➔ 右上アイコン ➔ 「マイ プロフィール」 ➔ 「API トークン」 を開く
- 「トークンを作成」 ➔ 「カスタム トークンを作成する」 を選択
- 以下の権限を設定して発行します:
| 区分 | 項目 | 権限レベル |
|---|---|---|
| アカウント (Account) | Cloudflare Pages | 編集 (Edit) |
| ゾーン (Zone) | キャッシュ パージ (Cache Purge) | パージ (Purge) |
| ゾーン (Zone) | ゾーン (Zone) | 読み取り (Read) |
- ゾーン リソース:
特定のゾーン➔ ご自身のドメイン(example.com)を選択 - 発行されたトークン文字列をコピーします。
ステップ 2: .env.local に環境変数を設定
フロントエンドのルートディレクトリにある .env.local に設定を追加します:
# Cloudflare API トークン
CLOUDFLARE_API_TOKEN=your_cloudflare_api_token_here
# 対象ドメインの Zone ID(Cloudflare ダッシュボードの概要画面右下に記載)
CLOUDFLARE_ZONE_ID=your_cloudflare_zone_id_here
ステップ 3: キャッシュパージ用スクリプトを作成
scripts/purge-cache.mjs を作成します:
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// .env.local から環境変数を読み込む
function loadEnv() {
const envPath = path.resolve(__dirname, '../.env.local');
if (!fs.existsSync(envPath)) return {};
const content = fs.readFileSync(envPath, 'utf-8');
const env = {};
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const [key, ...values] = trimmed.split('=');
if (key && values.length > 0) {
env[key.trim()] = values.join('=').trim().replace(/^["']|["']$/g, '');
}
}
return env;
}
const env = loadEnv();
const apiToken = process.env.CLOUDFLARE_API_TOKEN || env.CLOUDFLARE_API_TOKEN;
const zoneId = process.env.CLOUDFLARE_ZONE_ID || env.CLOUDFLARE_ZONE_ID;
async function purgeCache() {
console.log('\n🧹 [Cloudflare] CDN キャッシュのパージを開始します...');
if (!apiToken || !zoneId) {
console.error('❌ API トークンまたは Zone ID が設定されていません。');
process.exit(1);
}
try {
const response = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ purge_everything: true }),
});
const data = await response.json();
if (data.success) {
console.log('✨ [Cloudflare] キャッシュパージが完了しました!全世界のエッジが最新化されました。');
} else {
console.error('❌ [Cloudflare] パージ失敗:', JSON.stringify(data.errors, null, 2));
process.exit(1);
}
} catch (error) {
console.error('❌ 通信エラー:', error.message);
process.exit(1);
}
}
purgeCache();
ステップ 4: package.json のデプロイコマンドに連携
package.json の scripts を更新し、デプロイの直後に自動でパージが走るようにします:
{
"scripts": {
"build": "rm -rf .next/cache && next build",
"build:deploy": "rm -rf .next/cache && next build && wrangler pages deploy ./out && node scripts/purge-cache.mjs",
"deploy": "wrangler pages deploy ./out && node scripts/purge-cache.mjs",
"purge": "node scripts/purge-cache.mjs"
}
}
⚡ 疑問: 全体パージしてもサイトの表示速度は落ちないの?
「キャッシュを全消去(Purge Everything)したら、サイトの読み込みが遅くなるのでは?」と心配になるかもしれません。 結論から言うと、表示速度は一切落ちず爆速のまま です。
理由:JS / CSS はファイル名にハッシュが付いているため
Next.js がビルドする JS や CSS には 3h06tepri62mu.css のようにコードの中身から計算されたハッシュ値(固有ID)が付いています。
- 記事を追加しただけでは、JS や CSS のプログラム自体は変わらないため、ファイル名(ハッシュ)も全く同じ。
- Cloudflare CDN 側のキャッシュが消えても、訪問者のスマホやブラウザ内のローカルキャッシュ(第1層)は残っている。
- ブラウザは「このファイル名の JS/CSS は手元にある!」と判断し、自分の端末内から 0.001 秒で読み込む(from disk cache)。
つまり、「HTML(記事内容)は即座に最新化されつつ、重たい JS/CSS はローカルキャッシュから超高速表示される」 という理想的な動作になります。
🏆 まとめ
- Next.js の SSG ビルド時:
.next/cacheをクリアしてnext buildを実行する。 - Cloudflare CDN のエッジキャッシュ: デプロイ完了時に
purge-cache.mjsで自動パージする。 - JS/CSS の高速性: ハッシュ付きファイル(Immutable Cache)のおかげで、パージ後も訪問者側は爆速表示を維持。
この設定を入れておけば、約40秒後には全世界の訪問者に最新記事が 0 秒で反映されるようになります!