【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 を生成してしまっていました。

解決策

  1. 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' }),
    });
    
  2. 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 トークンを発行する

  1. Cloudflare ダッシュボード ➔ 右上アイコン ➔ 「マイ プロフィール」 ➔ 「API トークン」 を開く
  2. 「トークンを作成」 ➔ 「カスタム トークンを作成する」 を選択
  3. 以下の権限を設定して発行します:
区分 項目 権限レベル
アカウント (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.jsonscripts を更新し、デプロイの直後に自動でパージが走るようにします:

{
  "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)が付いています。

  1. 記事を追加しただけでは、JS や CSS のプログラム自体は変わらないため、ファイル名(ハッシュ)も全く同じ。
  2. Cloudflare CDN 側のキャッシュが消えても、訪問者のスマホやブラウザ内のローカルキャッシュ(第1層)は残っている。
  3. ブラウザは「このファイル名の 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 秒で反映されるようになります!