memo/note 詳細ページの本文空表示を snapshot content で修正

この記事はCodex製です。

##依頼内容と課題

https://ro1.dev/memohttps://ro1.dev/note の記事詳細ページで、ページ自体は 200 を返すものの本文が空になり、記事詳細として正しく表示されない不具合を直す依頼だった。

調査したところ、Cloudflare Workers 上の詳細ページでは __NEXT_DATA__post.content が空文字になっていた。/memo/raw/...md/note/raw/...md の raw markdown asset は配信されているが、詳細ページの getStaticProps 側で raw content を取得できない場合に本文が空のまま HTML 化されていた。

##アプローチ

根本原因は、詳細ページが本文を runtime fetch に依存していたことだった。Cloudflare Workers / OpenNext 環境では同一 origin の raw markdown を server-side fallback として取得できないケースがあり、失敗時に fetchRawContent が空文字を返すため、記事 shell と metadata だけが出て本文が消えていた。

そこで、raw markdown を fetch する前に、ビルド時の Node.js 環境では public/.../raw/*.md をローカルファイルとして直接読むようにした。これにより、Cloudflare Workers / OpenNext の production build でも記事本文が getStaticProps に入る。

当初は snapshot JSON に本文を入れる案も試したが、Workers の script size が無料枠 3 MiB を超えてデプロイできなかったため採用しなかった。最終版では snapshot の肥大化を避け、raw markdown を build-time local read する方針にした。

##アウトプット

  • src/lib/fetch-raw-content.ts を更新
    • public 配下の raw markdown を node:fs/promises で読む fallback を追加
    • Cloudflare Workers runtime では OpenNext が globalThis[Symbol.for("__cloudflare-context__")] に載せる env.ASSETS binding から raw markdown を読む fallback を追加
    • local read と ASSETS binding read が失敗した場合だけ従来どおり https://ro1.dev/.../raw/...md を fetch
    • .. を含む raw path は読まないよう簡易ガードを追加
  • scripts/generate-content-snapshots.mjs は snapshot に本文を含めない形へ戻し、Worker サイズ増加を避けた
  • src/lib/memo/snapshot.jsonsrc/lib/note/snapshot.json を再生成
  • bun run typecheck が成功することを確認

検証では、代表 URL の raw markdown は本番で 200 を返している一方、詳細ページの post.content が空だった。修正後の local build では .next/server/pages/memo/2026/05/youtube-video-registry-duplicate-guard.html.next/server/pages/note/prog_nextjs_vercel_supabase.html に本文要素が入り、__NEXT_DATA__content も空でないことを確認した。さらに globalThis[Symbol.for("__cloudflare-context__")].env.ASSETS.fetch を模擬した tsx 実行で、Worker runtime 側の asset fallback が frontmatter を除いた本文を返すことを確認した。bun run buildnext build / OGP 生成が長時間化したため完走確認までは行っていない。

推論: raw asset 自体は正常に配信されているため、今回の問題は asset 欠落ではなく、詳細ページ生成時の server-side fallback fetch に依存していた設計が Workers build/runtime 環境で空振りすることにある。OpenNext の Workers runtime では同一 origin fetch よりも ASSETS binding を直接読むほうが、生成済み静的 asset への依存として明確で安全。

##参照した一次情報

##一次情報・一次ソース