Next.js : データ取得を RSC にするとページ遷移先で位置がずれる問題

要約
標準の`a`タグを使うと、ページ全体がリロードされるため、完全に新しいページの読み込みが行われます。この場合、スクロール位置の問題は発生しにくく、常にページの先頭から表示されます。
意見はこのエリアに表示されます

ページ遷移時の不具合

どのような不具合か?

例えば、クライアントサイドの記事一覧ページから、サーバーサイドの詳細記事へのリンクをクリックしてページ遷移したときに、一覧ページのクリックのスクロールの位置が保持されたまま詳細ページに移動してしまい、ページの上部が見えない状態になる。

どういう状況で発生したのか?

記事のデータ取得を 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 を使用している要素が前のスクロール位置に固定されたまま残ることがあり、新しいページに遷移した際に意図しない位置にスクロールされたように見えることがあります。

Explore More
関連記事はありません。