Next.js : データ取得を RSC にするとページ遷移先で位置がずれる問題
ページ遷移時の不具合
どのような不具合か?
例えば、クライアントサイドの記事一覧ページから、サーバーサイドの詳細記事へのリンクをクリックしてページ遷移したときに、一覧ページのクリックのスクロールの位置が保持されたまま詳細ページに移動してしまい、ページの上部が見えない状態になる。
どういう状況で発生したのか?
記事のデータ取得を CSR から RSC に変更したタイミングで発生。
解決方法
Link コンポーネントを a タグに変更する。
例
なぜ a タグを使うのか?
a タグを使うことで、意図的に「完全に新しいページを読み込む」という挙動を維持できるので、スクロールのリセットなども期待通りに動作します。
Link コンポーネントはクライアントサイドでルーティングを行うため、ページのリロードを伴わずにページ間を移動でき、これにより非常に速い遷移が可能になります。
この SPA 的な動作で、状態を保持したままスムーズにページを移動できるのがメリットですが、それと同時に、ブラウザが持つ「前のスクロール位置を保持する」ような挙動も残ってしまう場合があります。このため、スクロール位置のリセットが期待通りに機能しないことがあるんですね。
なぜこの現象が起きたのか?
クライアントサイドルーティング(CSR)とサーバーサイドレンダリング(SSR)の間で発生する期待される挙動の違いによるものです。
Link コンポーネントは Next.js のクライアントサイドルーティングを活用するため、ページ遷移時に全体のページリロードを行わず、クライアント側の JavaScript が現在の DOM をそのまま維持しながら部分的に更新します。
一方、標準のa
タグを使うと、ページ全体がリロードされるため、完全に新しいページの読み込みが行われます。この場合、スクロール位置の問題は発生しにくく、常にページの先頭から表示されます。これはサーバーサイドレンダリングの利点を活かし、ページ全体を再度取得してレンダリングするためです。
要するに、Link コンポーネントは非常に効率的ですが、SPA 特有の「状態を維持したままのページ遷移」がスクロールのリセットに影響を与え、場合によっては期待通りに動作しないことがあるということです。このため、スクロール挙動を明示的に制御する必要が生じるのです。
もし、この問題で、UX が大きく損なわれている場合、どちらを優先すべきかを考える必要がありますね。
window.scrollTo はどうか?
微妙。
どうしても、SPA 的な挙動を維持しつつ、スクロール位置のリセットを行いたい場合は、useLayoutEffect
を使ってページが初めてロードされたときに上部にスクロールするように処理を追加することができます。
ただ、この方法だと、初期表示でページがずれた位置から、上にスルスルと上がるような摩訶不思議な挙動になります。
scroll={false} はどうか?
他の方法として、以下もありますが、これも遷移先のページが SSR のときは、スクロール位置がずれる問題が発生します。
CSS position: sticky を外す
position: sticky
は、スクロールの進行に応じて、要素がある特定の位置に留まるようにするレイアウトの手法です。以下の点がページ遷移時のスクロールに関連して問題になることがあります。
前のページの状態を引き継ぐことによる不具合: クライアントサイドルーティングを使用している場合、前のページの状態(DOMの要素やその位置)をそのまま次のページに引き継ぎます。
そのため、position: sticky
を使用している要素が前のスクロール位置に固定されたまま残ることがあり、新しいページに遷移した際に意図しない位置にスクロールされたように見えることがあります。