Vue+Vuetify+αで実装するSEOに適した無限スクロールの作り方

# はじめに

無限スクロールとは、TwitterやPinterestなどのSNSに代表されるような、ユーザーのスクロールに応じて続きのコンテンツが読み込まれ、ページ遷移を行わせることなくコンテンツを次々と表示させる技術ですが、[Googleによれば](https://webmaster-ja.googleblog.com/2014/02/infinite-scroll-search-friendly.html)無限スクロールはSEOやユーザービリティ対策として、ページの分割が可能なような仕様になるように推奨されています。

すなわち
http://example.com/?page=1
http://example.com/?page=2
などといった具合に分割して複数のページに分けることを出来るようにすることがよいというわけです。

実際無限スクロールを運用するに当たってはAjaxで一定の項目毎に取得すると思いますが、その取得するタイミングをページと見立て、読み込みが終わったタイミングで「History API」の pushState() や replaceState()を使ってURLを書き換えるのが一番簡単です。

しかし、項目の読み込みが遅い等の理由で無限スクロールのライブラリの発火ポイントを前めにするとURLの書き換えが早すぎたり、ページをバックしててURLが書き込まれないなどの不都合があります。
最初はスクロールした位置を取得してどうにかしようと悪戦苦闘をしましたが上手くいかずに、intersection-observerを活用するようにしました。

#実装

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="vYmqYzy" data-user="emesh" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/emesh/pen/vYmqYzy">
  Vue+Vuetify+αで実装するSEOに適した無限スクロールの作り方</a> by emesh (<a href="https://codepen.io/emesh">@emesh</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

まず、上をご覧下さい。jsfiddleの仕様ではURLは変更できないのでヘッダーにページ数が出ていますが、ある程度ページが進むとページ数が変わっているのがおわかりかと思います。なお、無限スクロールにはVueの定番ライブラリの[vue-infinite-loading](https://github.com/PeachScript/vue-infinite-loading)を、UIフレームワークとして[Vuetyfy](https://vuetifyjs.com/ja/)を採用しています。

##しくみ

```
  <div :data-src = "index" v-intersect="onIntersect"></div>
```

まずこのコードでJavaScriptに現在の位置を教えるコードになっています。私はVuetifyの[Intersection Observer API](https://vuetifyjs.com/ja/directives/intersect/)を使用しており、これはv-intersect="実行したい関数"を書くと容易にビューポート内に要素が来たときに発火するコードが実現可能です。リンク先のサンプルをみたら解るとおり、赤い点をスクロールするとビューポート内にカードが来たら色が緑に変更になる様子が見て取れます。

なお、v-for="(res,index) in items"のようにv-forには必ず配列のインデックスを加えてください。

```javascript:onIntersect.js
//ページの取得
onIntersect (entries, observer) {
    let res = {
            'ratio' : entries[0].intersectionRatio > 0,
            'dataSet': Number(entries[0].target.attributes['data-src'].value),
        }

    if(res.ratio && res.dataSet){
        //ページを算定
        let nowPage = Math.floor(res.dataSet / this.display) + 1 + this.firstPage

        //URLを取得
        let url_path = new URL(location.href);
        
        //1ページ以内ならpageを消す
        if(nowPage > 1){
            url_path.searchParams.set("page", nowPage)
        }else{
            url_path.searchParams.delete("page")
        }
        //WebApiに代入
        history.replaceState(null,null,url_path.toString());     
    }
},

```
次にJavaScript内ですが、res引数でintersectからの情報を引き出します。res.dataSetで配列のインデックスを取得しており、必ず数字形に変換し計算で現在のページを求め、それをURLクエリパラメーターに代入しreplaceStateを行っております。
### 注意点
今回は省きましたが、途中のページから読み取る実装の場合にはページ数を設定数する項目でthis.firstPage変数で初期のページ数を記憶させる必要があります。

## おわりに
以上簡単にSEO対策をした無限スクロールを紹介しました。多くの作例もありますが、リスト形式ではないグリッドのような複雑な無限スクロールだと高さが動的に変化することもあり、数日思案しておりましたがこのような比較的シンプルな作りにたどり着いたのは大変良かったと思います。皆様のご参考になれば幸いです。


### 参考文献
 + [Vue.jsでSEO対策した無限スクロール（Infinite Scroll）を実現する](https://rightcode.co.jp/blog/information-technology/vue-js-seo-infinite-scroll)
 + [Googleが推奨するSEOに適した無限スクロールの構成方法](https://www.suzukikenichi.com/blog/how-to-configure-search-engine-friendly-infinite-scroll/)
 + [VueJSでメモリマネジメントを加味した無限スクロールを実装](https://monteecristoo.hatenablog.com/entry/2021/02/18/042945)