技術とかの雑なToday I Learnedメモ

Next.js はどうやってスクロール位置を復元するのかを読んだ

Next.js はどうやってスクロール位置を復元するのかを読んだ

Next.jsはどうやってスクロール位置を復元するのか

読んだので軽く個人的まとめメモ。

scrollRestoration というフラグが experimental の機能で存在する

デフォルトでもブラウザ側でスクロール位置を復元してくれることもありますが、Safariでは復元されなかったり、ChromeでもgetServerSideProps利用時にはこのフラグを有効にしないとスクロール位置が復元されないなど不安定な状態です。

スクロール復元挙動

  • whatwg に記載がある
  • ブラウザの history entry には scroll position data という state がある
    • スクロール位置の情報を持っているっぽい
  • scroll restoration mode というモードがある
  • scroll restoration modeauto なら、 scroll position data をもとに user agent がスクロール位置を復元することができる。

Next.js の挙動

  • デフォルトだと experimental.scrollRestoration = false になっている
    • この場合の history.scrollRestorationauto
    • auto なら復元できるんじゃなかったっけ?と思ったけど SPA では auto でもブラウザの復元処理がうまくいかないことがあるらしい
  • getServerSideProps
    • getServerSideProps を含むページの遷移時やブラウザバック時には Next.js は getServerSideProps をサーバー側で実行する
    • props などの戻り値を取得するような fetch を実行するためブラウザバック時のレンダリングは非同期になる
      • ここがあまりピンときてないんだけど fetch の実行が終わるまでレンダリングが完了しないということだろうか
    • これ(非同期のレンダリング?)だとスクロール位置がブラウザによって復元されない
  • getStaicProps
    • これは getServerSideProps と同様にブラウザバック時に fetch が走る
    • fetch によって取得するのは静的な JSON であり、多くの場合は fetch 先から 304(Not Modified)が返る
    • Chrome ではスクロール位置が復元されたが Safari では復元されなかった
  • Data fetching を含まない場合
    • 動的な props 取得がないのでブラウザバック時のレンダリングで fetch が走らず、同期的にレンダリングできる
    • 結果は getStaicProps と同様に Chrome では復元されたが Safari ではされない

スクロール位置喪失と Navigation API

experimental.scrollRestoration の実装

  • 1 現在の履歴 (history) に対して key を発行
    • Next.js の next/linkuseRouter などで使われている Router クラスを使う
    • Router クラスは Next.js のライフサイクルに対する責務を持っている
    • この Router 内で新規の履歴に対してユニークな key を作成する
    • もともとは単純な index だったが簡単に破綻するため、記事の筆者が修正のプルリクエストを送りマージされたらしい、すごい
  • 2 遷移前の key とスクロール位置を Session Storage に保存
    • 遷移前の key を用いて Session Storage に __next_scroll_${key} という名前で保存する
  • 3 ブラウザバック / ブラウザフォワード時に key をもとにスクロール位置を取得
    • __next_scroll_${key} で getItem して、値があればスクロール位置の情報として取得する
  • 4 Next.js の render 処理でスクロール位置を復元
    • レンダリング後の useLayoutEffect で window.scrollTo に取得したスクロール位置の情報を渡す

残課題

  • ブラウザバック / ブラウザフォワード時については問題ないが、リロード時には Router の key がリセットされてしまうので復元できない
    • これについては記事の著者がまたまた Next.js にプルリクエストを送っているらしい、すごい

Navigation API の利用

  • 将来的に Navigation API が普及した場合

Navigation APIではnavigateイベント時に遷移を定義できるようになります。遷移を定義できるというのは、ブラウザ側に「遷移の開始〜終了」をPromiseで渡すAPIを用意することで、ブラウザ側がMPAでしかできなかったローダーの表示やブラウザによるスクロール位置の復元を行えるようになることを意味します。

必要であれば、e.restoreScroll()というメソッドを呼び出せばtransitionWhileに渡すPromiseの任意のタイミングでスクロール位置を復元できるので、履歴のkey管理やSession Storageへのスクロール位置情報の格納は不要になります。