Nuxt 3 でページ遷移したら遷移後にちゃんとスクロール位置をトップにさせる

Nuxt 3 というか厳密には Vue Router の話ではあるんですが、「ある程度下へスクロールした状態でサイト内リンクをクリックして遷移した場合、遷移先でページの一番上にスクロールしてくれない(ことがある)」という問題があります。

やや脱線しますが、自分はこれを SPA における一番の問題点だと考えていて、つまり旧来のウェブでできていた普通の挙動と体験(=リンクをクリックしたら新しいページが一番上のスクロール状態で開かれる)が損なわれるようではこれほど本末転倒なことはないということですね。

なので Nuxt や Vue に限らず、もっともっと根本的なソリューションを天才たちが生み出してくれることを願っている…。

嘆いても仕方がないので、とりあえず自然な見た目になるようになんとかします。

自分はいまこうしている

先に(自分向けの)解決策だけ書きます。詳しい背景は次の項で書いてあります。

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook("page:start", async () => {
    window.scrollTo({
      top: 0,
      left: 0,
    })
  })
})

これを page-scroll.ts というファイル名で plugins ディレクトリに保存すれば OK です。

このコードを使う際の前提条件もいくつかあります。

  • SSG である
  • pageTransition を使わない

ちなみにいまお読みのこのサイトも上記が適用されている状態です。

Nuxt 3 での scroll top 問題の変遷

Nuxt 2 時代では、実際には Vue Router が抱えているこの問題をラップするような形で(一応の)解決策が与えられていました:

<template>
  <h1>My child component</h1>
</template>

<script>
  export default {
    scrollToTop: true
  }
</script>

Nuxt 3 は RC 版が出始めたころからずっとこのような手段がなく、当時から該当の disucussions スレッドは来訪者が絶えません。

scrollToTop in v3 ? · nuxt/nuxt · Discussion #16223
scrollToTop in v3 ? · nuxt/nuxt · Discussion #16223
v3 not support scrollToTop: true ?

自分も初期からずっとここをウォッチしているのですが、みんな書き込まれているワークアラウンド用スニペットに独自のアレンジをちょっとだけ加えてまた書き込む…みたいな感じで、むしろ大差なさそうなのにどれを選ぶべきか難しくなっている状態に思えます。

自分が Nuxt 3 を使う用事(いまのところ SSG だけ)的には必要最低限の部分を把握することができたので、それを先ほど紹介しました。再掲します。

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook("page:start", async () => {
    window.scrollTo({
      top: 0,
      left: 0,
    })
  })
})

Nuxt が提供するフックを使用してあとは単なる JS でスクロール位置を制御しているだけです。

フックの選択として page:finish というのも候補に上がりそうな気がしますが(事実これを書いているレスも上記スレッド内に多くある)、自分が試した限りでは savedPosition が返らないので戻るボタン使用時に意図した挙動になりませんでした。

下記 2 つの条件がなぜ存在するかの話も書いておきます。

  • SSG である
  • pageTransition を使わない

ページのフック、そしてデータがフェッチされるタイミングの関係的に、ページ遷移がまだされていないのに先に一番上にスクロールされてからページが移り変わるような挙動になってしまうことがよくあります。

SSG だとページの遷移と表示がほぼ同時なくらい速いのでこの問題を認識することもない、ということです。

また、pageTransition はこの問題とすこぶる相性が悪く、どう試しても

  1. リンクをクリックする
  2. スクロール位置がトップに移動する
  3. ページが切り替わる
  4. ページトランジションが動作する

のようになってしまいます。

本ブログでは pageTransition は使っておらず上部に <NuxtLoadingIndicator> があるのみですが、別の Nuxt 3 製サイトでは手動マークアップによるオリジナルのページトランジション的な実装をしています。

まとめると、SSR 形式のサイトやその他の条件の場合、Nuxt 3 ではスクロールトップ問題を他の人がどう解決しているのかは全く知りません。というかこれがいまもよくわからない状態であることに普通にびっくりする…。

Nuxt 3 を触り始めたころからずっと気になっていたので、唯一確実に Nuxt 3 製だと思われた公式ドキュメントのサイトをクリックしまくったりしていのですが、このサイトもやっぱり読み込みに時間がかかるページではトップへの移動と遷移は同時には行われないケースを目撃できていました。

いまの公式サイトは少なくとも SSR モードだと思うんですが、この感じだとページ読み込み速度に関わらず普通にワークしているようには見えますね。もうちょっと色々情報を公開してほしいんだけどな、Nuxt 周辺は本当にリファレンスが、、、

 

ちなみに、基本的には Vue Router 単体でいえばこの問題はうまく解決されていて、すべてのルートナビゲーション時にはスクロール位置を一番上まで移動させ、ヒストリー遷移した場合には保存された位置に戻ることも可能です。

scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

冒頭から提示している Nuxt 3 用の解決策と似ていますね。詳細は下記で。

https://v3.router.vuejs.org/ja/guide/advanced/scroll-behavior.html
https://v3.router.vuejs.org/ja/guide/advanced/scroll-behavior.html

あと、どこで読んだか思い出せずソースを掲載できないんですが、Nuxt 3 もこの問題に対して公式の対応策を考えている(可能性がある)みたいな感じだった気がします。
(ちなみに Nuxt 3 のコンポーネントオプションのページ には、scrollToTop という項目だけがあり、未サポートの旨も書かれています)