• #architecture
  • #nuxt
  • #ssg
  • #scalability
  • #未解決課題
開発完了

背景

2026年1月1日、GitHub ActionsでのデプロイがNode.js OOM(Out of Memory)エラーで連続失敗した。調査の結果、以下が判明:

  • 3ヶ月分のコンテンツ(338ファイル)で約2GBのメモリを使用
  • GitHub Actions無料枠のRAMは7GB
  • SSGは全ページをメモリに読み込んで処理する

問題の本質

SSGはコンテンツ量に比例してビルド時メモリが増加する

単純計算での予測:

期間メモリ使用量GitHub Actions対応
3ヶ月約2GBOK
1年約8GB限界超過(7GB上限)
3年約24GB不可能

即時対応(実施済み)

  1. Node.jsヒープメモリを4GBに拡大
    "generate": "NODE_OPTIONS='--max-old-space-size=4096' nuxt generate"
    
  2. 巨大ファイルをプリレンダリングから除外
    nitro: {
      prerender: {
        ignore: [/\?/, /\/prompts\//],
      }
    }
    

長期対策オプション

オプション1: ハイブリッドレンダリング(推奨)

最新コンテンツはSSG、古いコンテンツはISR(Incremental Static Regeneration)で処理。

// nuxt.config.ts
routeRules: {
  // 最新1年分はSSG(事前生成、爆速)
  '/2025-**': { prerender: true },
  '/2026-**': { prerender: true },

  // 古い記事はISR(初回アクセス時に生成してキャッシュ)
  '/2024-**': { isr: 3600 },   // 1時間キャッシュ
  '/2023-**': { isr: 86400 },  // 1日キャッシュ
}

メリット:

  • ビルド時は最新1年分のみ生成 → メモリ問題解決
  • 古い記事も初回アクセス後はキャッシュ → 2回目以降は爆速
  • Cloudflare Workersのエッジキャッシュで高速配信

デメリット:

  • 古い記事の初回アクセスは少し遅い(ただし、古いログを見る人は待てる)
  • cloudflare-pages-staticからcloudflare-pagesへの変更が必要

オプション2: ローカルビルド + デプロイ

ローカルマシン(16GB〜32GB RAM)でビルドし、結果をデプロイ。

方法A: distをGitHubにコミット

# ローカルでビルド
pnpm generate

# distをコミット
git add dist/
git commit -m "Build: update static files"
git push

# GitHub ActionsはdistをそのままCloudflareにデプロイ

方法B: ローカルから直接デプロイ(今回採用)

# ローカルでビルド&デプロイ
pnpm generate
wrangler pages deploy dist --project-name=mdx-playground

メリット:

  • ローカルマシンのメモリを使用 → 制限なし
  • 全コンテンツをSSGで事前生成可能
  • 初期表示が常に爆速

デメリット:

  • distディレクトリが大きい(数百MB〜数GB)
  • 方法Aの場合、Gitリポジトリが肥大化
  • 自動デプロイではなく手動操作が必要

オプション3: 完全SSRへの移行

全ページをサーバーサイドレンダリングに移行。

// nuxt.config.ts
nitro: {
  preset: "cloudflare-pages", // staticを外す
}

メリット:

  • ビルド時のメモリ問題が根本的に解消
  • コンテンツ量に関係なくスケール可能

デメリット:

  • 全ページで初期表示が遅くなる
  • サーバーコスト増加の可能性

オプション4: ビルドマシン強化

GitHub Actions有料版やセルフホストランナーを使用。

  • GitHub Actions有料版: 最大32GB RAMまで選択可能
  • セルフホストランナー: 自前サーバーで無制限

メリット:

  • 既存のSSG構成を変更不要

デメリット:

  • コストがかかる

採用したアプローチ: ローカルビルド + 直接デプロイ

検討の結果、オプション2B(ローカルから直接デプロイ) を採用。

変更前の構成(GitHub Actions経由)

ローカル → GitHub push → GitHub Actions(ビルド)→ Cloudflare

問題点:

  • GitHub Actions無料枠のRAM制限(7GB)
  • 1年程度でメモリ上限に達する見込み
  • ビルド待ち時間が発生

変更後の構成(ローカル直接デプロイ)

ローカル → GitHub push(ソースコードのバージョン管理のみ)
ローカル(ビルド)→ Cloudflare直接デプロイ

メリット:

  • ローカルマシンのメモリを使用(16GB〜32GB)→ 何年分でもビルド可能
  • GitHub Actionsの待ち時間なし
  • デプロイが速い(数秒〜数十秒)
  • Cloudflare Pages無料枠で無制限に運用可能

デメリット:

  • 自動デプロイではなくなる(手動でコマンド実行)

デプロイ手順

# ビルド&デプロイ(1コマンド)
pnpm deploy:cloudflare

または個別に:

pnpm generate                                           # ビルド
wrangler pages deploy dist --project-name=mdx-playground  # デプロイ

運用ルール(再現性確保):

  • デプロイ前に git status でクリーンな状態を確認
  • 未コミットの変更がある状態でデプロイしない
  • どのコミットからビルドしたかはGitのログで追跡可能

Cloudflare Pages無料枠の制限

項目制限
静的ファイルホスティング無制限
帯域幅無制限

| リクエスト数 | 無制限 | | サイトサイズ | 実質無制限 | | ビルド回数 | 500回/月(ローカルビルドなら無関係) |

結論: ローカルビルド + 直接デプロイなら、何年でも無料で運用可能(ただしCloudflareの料金体系変更の可能性はある)

GitHub Actionsの役割変更

  • 変更前: ビルド&デプロイ
  • 変更後: CI(テスト、lint等)のみ、またはワークフロー削除

今後の選択肢

ローカルビルドが遅くなりすぎた場合(10分以上など):

  • ハイブリッドレンダリング(オプション1)への移行を検討
  • 最新1年分のみSSG、古い記事はISR

ハイブリッド移行時の注意点:

  • routeRulesの年指定(/2025-**等)は毎年手動更新が必要
  • 更新忘れを防ぐには、現在年から動的に計算するコードに変更する

関連ドキュメント

補足: なぜログデータはISRで問題ないか

このサイトのコンテンツは日付ベースのログ/メモ形式。

  • 最新の記事: 頻繁にアクセスされる → SSGで爆速が重要
  • 1年以上前の記事: たまにしかアクセスされない → 初回だけ少し遅くても許容範囲
  • 古いログを見る人: 「待てる」ユーザー層

この特性を活かして、ハイブリッドレンダリングが最適解となる。