mizdra's blog

ぽよぐらみんぐ

Next.js 14.0.0+ で Pages Router / basePath / Middleware 併用時に発生する不具合とその回避策について

社内のあるプロダクトで Next.js を v13 系から v15 系にアップデートしたところ、トップページにブラウザバックで戻ると、エラーが発生するようになってしまった。

エラーの原因を調べてみると、何故かトップページにブラウザバックで戻った時、pageProps が空オブジェクトになっているようだった。

不具合再現の様子。ブラウザバックでトップに戻ると、pageProps が空になる。

実はこれは Next.js の不具合で、以下に issue がある (まあ僕が報告したのだけど...)。

github.com

Next.js 側で修正されるのを待ってたのだけど、不具合の報告から1年以上経っても何の音沙汰もない。困り果てたので、なんとか不具合を回避する方法がないか、探すことにした。

不具合の発生条件

この不具合はいつでも再現する訳ではなくて、以下の条件をすべて満たす時発生するようになってる。

  • Next.js 14.0.0+ を使っている
  • basePath オプションを使っている
  • Middleware を使っている
  • トップページを Pages Router でレンダーしている
  • トップページへ soft navigation で遷移する

すべて満たす時発生するので、どれかが欠けると発生しない。つまり発生要件のどれかを満たさないようにすれば、問題を回避できるはず。そう考えて、どれかを満たさないようにする方法がないか模索することにした。

回避策の検討

基本的には、以下のどれかが回避策として利用できるはず。

  • 方法1: basePath オプションの使用をやめる
  • 方法2: Middleware の使用をやめる
  • 方法3: Pages Router の代わりに App Router でトップページをレンダーする
  • 方法4:トップページへ hard navigation で遷移する

ただ方法1については、我々のプロダクトではbasePath オプションがどうしても必要なプロダクトだったため、採用できなかった。方法2も、プロダクトの重要なところで Middleware を使っていて他の技術への置き換えが難しく、採用できなかった。

方法3は将来性があって優れてるけど、Pages Router から App Router に移行するのが大変なのが気がかりだった。我々のプロダクトでは getServerSideProps に複雑なロジックが書かれているのだけど、getServerSideProps は App Router にはない。App Router では getServerSideProps に書いていたロジックを Server Component に書くことになる。だいぶ書き方が違うため、どうにも移行が難しかった。他に良い方法がないなら選択しても良いけど、気軽に選びにくい方法だった。

他に何か良い方法がないか...と悩みに悩んで思いついたのが方法4だった。トップページへ soft navigation で遷移すると不具合が発生するのだから、hard navigation で遷移したら不具合は発生しないはず。これならアプリケーションの機能を殆ど損なうこともなく、大幅なコードの書き換えもなく導入できるはず。そう考えて、方法4を採用することにした。

回避策の実装

以下に全てが書いてあるので、それを見て欲しい!

重要なポイントは以下。

  • <Link> component をラップし、トップページの遷移を hard navigation に fallback させる
    • import Link from 'next/link'import {Link} from '@/components/Link' に置き換える
  • useRouter() をラップし、トップページの遷移を hard navigation に fallback させる
    • import {useRouter} from 'next/link'import {useRouter} from '@/lib/useRouter' に置き換える
  • Router.beforePopState を使用し、トップページへのブラウザバック/フォワードをキャンセルし、ハードナビゲーションに fallback させる

とにかく soft navigation でトップページに遷移してしまう経路を握りつぶしたら良い。import 文の書き換えがちょっと面倒ではあるけど、 ts-fix で足りない import を自動で追加してみる のような手法で機械的に書き換えたら良いはず。

一応手元で確認した限りは、これで問題なく不具合を回避できた。

回避策を導入した後の様子。トップに遷移しても pageProps が空にならない。

おわりに

この記事で紹介した回避策は、僕の社内のプロダクトで必要に迫られて導入したものだったけど、多分世の中にも必要なプロダクトがたくさんあると思う。つい最近もかなり古い Next.js バージョンを使ってる、basePath を利用するプロダクトを見かけた。真相はわからないけど、僕と同じような不具合にハマってバージョン上げられてないのかなと想像していた。

この記事がそういう人のためになればと思う。

おまけ

basePath オプションは他にも奇妙な不具合がある。Pages Router / basePath / rewrites / Middleware 併用時に発生する以下の不具合とか。何故かある長さより小さいパスのページにするとエラーになる。5文字のパスのページには遷移できるけど、4文字のパスのページには遷移できない、という不思議すぎる挙動をする...

github.com

basePath が悪いのか、Pages Router を使っているのが悪いのか分からないけど、とにかくそれ関連の組み合わせがめちゃめちゃバグっている印象がある。Pages Router がもうアクティブに開発されないことを考慮すると、今後も新しい Next.js で basePath / Pages Router 併用時に発生する新たなバグが混入して、それが修正されず放置される可能性も高いだろう。

将来を見据えるなら、今回の記事で紹介したような一時しのぎ的な解決策ではなく、App Router への移行をすべきだと思う。あと basePath 使わずに済むならそれに越したことはない。まあそれが難しいから皆困ってるんだろうけど...

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.