• #Nuxt
  • #SSG
  • #Vite
  • #ビルドエラー
  • #Cloudflare Pages
  • #トラブルシューティング
開発mdx-playgroundメモ

Nuxt SSGビルドエラーを3つ潰して本番デプロイを復旧した

本番サイトを開いたら、2月22日以降の記事がごっそり消えていた。ブラウザのキャッシュかと思いシークレットモードで確認しても同じ。devサーバーでは全記事が表示されているのに、Cloudflare Pagesにデプロイされた本番だけがおかしい。ログを追い始めたら、原因が3つ重なっていた。

原因1: importパスの階層が1段足りなかった

tax-patterns/ 配下の4つのVueファイル(curated.vue, index.vue, quiz.vue, voucher-demo.vue)で、internal/data/extracted/ への相対importパスが1階層不足していた。

// NG: 4階層では届かない
import { ... } from '../../../../internal/data/extracted/curated-200'

// OK: 5階層必要
import { ... } from '../../../../../internal/data/extracted/curated-200'

pages/tax-patterns/curated.vue から internal/ に到達するには pages/ → app/ → web/ → apps/ → root/ の5段登る必要がある。4段だとViteがモジュールを解決できず、ビルドがそこで止まる。

この1つのエラーがViteのビルドプロセス全体を壊し、全ページのSSG生成が中断されていた。devサーバーではHMRが個別にモジュールを解決するため問題が表面化せず、本番ビルド時にだけ爆発するという厄介なパターンだった。

原因2: BlogCalendar.vueのDate型問題

BlogCalendar.vuegetDisplayDate() が、z.coerce.date() で返ってくるDate型オブジェクトを文字列として .split('T') していた。

// 修正後: String()でDate→文字列に変換してからsplit
const getDisplayDate = (article: Article): string | null => {
  const date = article.updatedAt || article.publishedAt
  if (!date) return null
  return String(date).split('T')[0]
}

content.config.tspublishedAt: z.coerce.date() と定義しているため、ランタイムでは Date オブジェクトが返る。Date.prototype.split は存在しないので、カレンダー表示がクラッシュしていた。String() でラップすることで toISOString() 相当の文字列に変換してから split('T') する形に修正。

原因3: 未コミットのコンテンツが本番に含まれていなかった

2月26日以降のコンテンツファイル群がgit管理外のままだった。ローカルのdevサーバーではワーキングツリーのファイルを直接読むため表示されるが、Cloudflare Pagesのビルドはgitリポジトリからチェックアウトするため、未コミットのファイルは当然含まれない。

git status で確認したら apps/web/content/2026-02-26/ 以降のディレクトリが全て ??(untracked)だった。コミットしてプッシュすることで本番に反映。

付随して修正したもの

make-diaryスキルのドキュメント改善

日記作成スキルのドキュメントに、categoryproject_name のバリデーション情報を追記した。content.config.tsz.enum() に定義されていない値を指定するとZodバリデーションエラーでビルドが壊れるため、有効な値の一覧をスキル側にも持たせた。

content-managementスキルのcategory表にdiaryが欠落

content-managementスキルのcategory表に "diary" が載っていなかった。content.config.ts には z.enum(["personal", "dev", "diary"]) と定義されているのに、スキルのドキュメントには "personal""dev" しか記載がなかった。追記して整合性を取った。

前日の日記の404問題

前日の日記(diary-2026-03-04)へのクライアントサイドナビゲーションで404が返る問題があった。ブラウザのアドレスバーに直接URLを入力すると表示される。devサーバーを再起動したら解消した。Nuxt Contentのインメモリキャッシュが古い状態を保持していたと推測される。

振り返り

devサーバーで動いているのに本番で壊れる、という状況で2週間気づかずに放置していた。「pnpm build をローカルで回してから記事を書く」という習慣がなかったのが根本原因で、CIでビルドチェックを走らせるか、デプロイ前にローカルビルドを通すフローを入れるべきだった。

相対importパスの階層ミスは、エディタの補完に頼っていると見落とす。ファイルを pages/ の深い階層に置いた時点で、importパスを手動で数え直す癖をつけたい。TypeScriptのpath aliasを使えばそもそもこの問題は起きないが、モノレポで internal/ ディレクトリを参照する都合上、相対パスに頼っている部分が残っている。