紡Book の勉強ノートに戻る
コードレビュー学習2026.05.11

ライブラリの版を変えても、ビルドが落ちる場所が移動するだけだった — motion + React 19 + Next.js 16 の SSR エラー調査

Next.jsReactmotionSSRprerenderinvestigation

ビルドが落ちる原因を半日掘って「これは半日では直せない」と判断した investigation の記録です(修正は別セッション)。

この記事でわかること

  • Next.js 16 + React 19 + motion の組み合わせで起きる useContext null の正体
  • ライブラリ版を変えると失敗ページが移動するだけで根本解決しない罠
  • investigation の進め方と「直すか、撤退して文書化するか」の判断
  • ラビットホールに入った時の revert + 詳細ドキュメント化 という選択肢

起きていた症状

next build を回すと最後にこれ:

Error occurred prerendering page "/_global-error". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot read properties of null (reading 'useContext')
    at ignore-listed frames {
  digest: '3333581645'
}
Export encountered an error on /_global-error/page: /_global-error, exiting the build.

ポイント:

  • 落ちてるのは /_global-error(Next.js が自動生成する内部 route)
  • ユーザコードの global-error.tsx は書いてあるが、これ自体は使われない(Next.js 内部の auto-generated 版が落ちてる)
  • React の useContext が null を返している = React そのものが解決されていない

検索で出てくる既知 issue

このエラーで GitHub Issue を漁ると複数ヒット:

  • #84994: Next.js 16 canary.7 で useContext null during /_global-error SSR prerender
  • #85668: 16.0.1 でも同じ
  • #86178: 16.0.2 / 16.0.3 でも
  • #87719: next build fails while prerendering internal /_global-error route

つまりフレームワーク側の既知バグ。自分のコードの問題じゃない。

ただ「何のライブラリが原因の引き金か」はプロジェクトごとに違います。Issue を読むと「Framer Motion を抜いたら直った」「特定の Client Component を消したら直った」など。

→ このプロジェクトでは motion@13.0.0-alpha.0 が疑い濃厚。

(そもそもなぜ安定版でなく alpha を使っていたかは、別記事 motion + React 19 で MotionValue が style に反映されない。13.0.0-alpha.0 で逃げた話 の経緯。React 19 対応のために上げた alpha が、今度はビルドで足を引っぱっている形。)

Webpack でも同じ

「Turbopack の問題かも」と思って next build --webpack で試したが、同じエラー:

TypeError: Cannot read properties of null (reading 'useContext')
    at C (.next/server/chunks/465.js:13:9654)

= Turbopack 起因ではなく、共有 chunk に含まれるライブラリが SSR/prerender 中に React を解決できていない。

motion のダウングレード実験

motion@13.0.0-alpha.0 → motion@12.38.0 (最新安定版) にダウングレードして再ビルド:

Error occurred prerendering page "/concept". 
TypeError: Cannot read properties of null (reading 'useContext')

今度は /concept で落ちる。/_global-error は通った。

= どっちの版でも、どこかが落ちる。「正解の版」は無い。

さらに段階的に追ってみる

組み立て場所は3つ。どれも実行時メモリで、DB にも prefs にも保存しない。作り置き(prerender)next build のプロセスメモリReact の窓口 = null。ここで転ぶ保存しないforce-dynamicssr:false注文後 SSRサーバの実行時メモリReact は用意済み。通る。保存しないブラウザブラウザのメモリReact は用意済み。通る。保存しないmotion は共有 chunk(.next/server/chunks の JS ファイル)3つの工程が同じファイルを読む。DB には無い。矢印は交差で、置き場所ではない

motion 12.38 のまま、failing page の /concept に force-dynamic を当てる:

ts
// app/(marketing)/concept/page.tsx
export const dynamic = "force-dynamic"; // このページは作り置きをやめて、注文が来てから毎回作る、の指定

ビルド時 prerender を skip して、リクエスト時 SSR にする。

→ /concept は通った。今度は /creation-notes/blog/[slug] で落ちる。

更に Navigation を ssr:false なラッパー化(dynamic(() => import('./NavigationInner'), { ssr: false })):

→ blog は通った。今度は /_not-found で落ちる。

「失敗箇所が移動するだけ」構造

転ぶページだけが右へ移る。motion は共有 chunk の JS ファイル。DB にも prefs にも無い。12.38 へforce-dynamicssr:false0 /_global-error13.0.0-alpha.0転ぶworkaround なし1 /concept12.38.0転ぶworkaround なし2 blog/[slug]12.38.0転ぶ(marketing) に force-dynamic3 /_not-found12.38.0転ぶNavigation を ssr:falsemotion は共有 chunk(.next/server/chunks の JS ファイル)4つのページの下を貫く。ここは動かない。DB にも prefs にも保存しない。矢印は交差で、置き場所ではない

1個直すと別が落ちます。motion を使う直 import が共有 chunk に居る限り、Next.js auto-generated な内部 route のどこかで必ず落ちます。

これは「ライブラリのアップデートで直す」「ピンポイント workaround で逃げる」では本質的に解決しません。

4 つの選択肢

ここまで掘った時点で、残ってる選択肢:

案 内容 コスト
A motion 使う全ファイル (13ファイル) を ssr:false ラッパー化 1日級。SSG 消失 = SEO トレードオフ
B Next.js 16.3 以降を待つ(バグ修正される可能性) 0。リリース時期不明
C Next.js 15.5 にダウングレード 1日級。API 互換性検証要
D motion を別ライブラリに置換(framer-motion 旧版 / react-spring / CSS) 1〜2日級。アニメ挙動の微調整要

どれも半日では終わらない。

撤退の判断

ここで時間切れの感覚があった:

  • 投資して半日で「直る確証はない」状態
  • 公開ローンチ前マスト残り 1 件(OAuth only 化)が控えている
  • 5/13 認定日が控えていて、深掘りで体力を使うとチェーンが崩れる

→ revert + 詳細ドキュメント化に切り替える判断:

  1. この investigation で足した実験変更を revert(motion 13.0.0-alpha.0 に戻す / Navigation を直 import に戻す / force-dynamic を外す)。※ ただし investigation より前から入っていた concept の ssr:false 退避(ConceptClient.tsx・「安定版 motion に上げたら通常 import に戻す」コメント付き)はそのまま残置=完全な素の状態に戻すわけではない
  2. タスク.md に 段階的検証ログ + 4 選択肢 + 推奨進め方 を全部書く
  3. 次のセッションが文脈ゼロから入れる状態にする
  4. このセッションでは OAuth only 化(残り 1 マスト)に進む

完了の定義を「実装完了」から「investigation 完了 + 次の人への引き継ぎ完了」に再定義した形。

ラビットホール撤退の価値

「直さなかった」のは負けに見えるが、次のセッションがゼロから再調査しなくていい状態を残せた価値はある:

  • どの版で何が落ちるか → 4ステップの実験ログを記録
  • どんな workaround は効かないか → ssr:false / force-dynamic を個別に試した結果
  • 残ってる選択肢は何か → A/B/C/D の比較
  • 次にやるべきこと → 推奨フローを4段階で

→ 次に着火する時、ここから始められる。「掘った時間が無駄」にはならない。

学びポイント

  • 「Cannot read properties of null (reading 'useContext')」は React そのものが null = モジュール解決の失敗を示します
  • ライブラリのアップデート / ダウングレードで 失敗箇所が移動するだけ の場合があります(= 根本問題はもっと深い場所にあります)
  • Next.js auto-generated な 内部 route(/_global-error / /_not-found)が落ちる場合、自分のコードだけでは fix できない可能性が高いです
  • 半日掘って解決の糸口が見えなければ、撤退 + 詳細ドキュメント化が有効な選択肢
  • 「実装した」じゃなく「次の人がゼロから始めなくていい状態を残した」も valid な完了基準

おわりに

エンジニアリングの判断は「直すか / 直さないか」だけじゃなくて、「今直すか / 次に直すか / 何のために調べた結果を残すか」も入ります。

ラビットホールに入った時に「深さの感覚」と「撤退のタイミング」を持てると、トータルでの生産性が上がります。

実装中の自分から、次にこのコードを開く自分への手紙を詳細に書きます。それで「未完了の問題」が「次に進める問題」になります。


この記事は2026年5月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。

追記(時点メモ): /_global-error の prerender 落ちは、本番(Vercel)では dynamic レンダリングへ fallback されてデプロイ自体は通っており、本番サイトへの影響はゼロ。根本解決は Next.js 16.3 以降の修正待ちで、執筆時点では未解決のまま(concept の ssr:false 退避で運用を回している)。