無限スクロールの下部がインデックスされない原因と直し方
一覧の上の方は検索に出る。下までスクロールして出てくる商品や記事は、いつまでも出てこない。無限スクロールを入れたサイトでよく聞く症状です。
答えを先に書きます。疑うべきは「続きの中身に、固有のURLと、そこへ向かうhref付きのaタグがあるか」です。Googleの公式ドキュメントは、ページ分割では各ページに固有のURLと、順番にたどれるリンクを持たせるよう書いています。無限スクロールについても、Google検索セントラルのブログ記事が、中身をいくつかのページ(コンポーネントページ)に分け、それぞれに固有の完全なURLを用意する方法を推奨しています。スクロールしたときだけ中身が増え、URLもリンクも無い作りは、この形から外れています。
なぜ「スクロールで出てくる中身」が拾われにくいのか
ブラウザで見る人には何の問題もありません。スクロールすれば続きが出ます。では、Googleからは何が違って見えるのか。
公式ドキュメント「JavaScript SEO の基本」によると、GoogleはJavaScriptを使うページを「クロール→レンダリング→インデックス登録」の流れで処理します。レンダリングとは、HTMLとJavaScriptを実行して画面を組み立てる処理です。同じドキュメントには、レンダリングした後のHTMLに中身が見えなければ、Googlebot(Googleの巡回ロボット)はそれをインデックスに含められない、という趣旨の記述があります。
ここが分かれ目です。スクロールという操作をきっかけに続きを読み込む作りの場合、レンダリング後のHTMLに2ページ目以降の中身が入っているとは限りません。入っていなければ、その中身は検索の対象になりにくい状態です。人がスクロールしたときに起きることを、巡回ロボットも同じように起こしてくれる、という前提には立たないほうが安全です。
もう一つの入口がリンクです。公式ドキュメント「Google がリンクをクロールできるようにする」は、Googleがリンクをページの関連性の把握と新しいページの発見に使うこと、そしてリンクをクロール可能にするには href属性を持つa要素 を使うことを案内しています。スクロールで続きを読み込むだけの一覧には、たいてい「次の中身」へのaタグがありません。中身はHTMLに無い。たどるリンクも無い。この二重の欠落が、下部がインデックスされない典型的な形です。
公式が示す「検索エンジンに優しい無限スクロール」の形
では、どう作れば良いのか。2014年の公式ブログ記事と、現在のページ分割のドキュメントを合わせると、押さえるべき点は次の表に収まります。
| 要素 | 公式の記述(要旨) | 実装で確かめること |
|---|---|---|
| ページ分割 | 無限スクロールの中身を、コンポーネントページの連なりに分ける(ブログ記事) | 1つの長い一覧を「◯件ずつ」の単位に区切れているか |
| 固有のURL | 各ページは、利用者の閲覧履歴に頼らず直接その中身にアクセスできる、固有の完全なURLを持つ(ブログ記事)/各ページに固有のURLを(ドキュメント) | ?page=3 を新しいタブで直接開いて、3ページ目の中身が出るか |
| 順にたどれるリンク | 各ページに固有のURLと、順番にたどれるリンクを(ドキュメント) | 各ページのHTMLに、前後のページへのhref付きaタグがあるか |
| 重複させない | ページ同士で中身が重ならないよう分ける(バッファ分を除く)。重なりがあると重複した中身のクロール・インデックスにつながる(ブログ記事) | 1つの商品・記事が、1つのページにだけ載っているか |
| URLの更新 | 無限スクロールのページで replaceState と pushState を実装する。どちらを使うかはサイトと利用者の行動しだいだが、pushState を含めることを推奨(ブログ記事) | スクロールして3ページ目の範囲に来たら、アドレスバーが ?page=3 に変わるか |
| タイトル | 連なりの各ページは、似た title タグを持つ(ブログ記事) | 各ページの title が一覧名+ページ番号のような形になっているか |
| 代表URL | 1ページ目を代表URL(canonical)にしない。同じ一覧の絞り込み・並べ替え版はインデックスさせない(ドキュメント) | 2ページ目以降の canonical が1ページ目を向いていないか |
表の中で、いちばん効いてくるのは「固有のURL」と「順にたどれるリンク」の2行です。この2つがそろえば、無限スクロールは「ページ送りの一覧に、スクロールで読める見た目をかぶせたもの」になります。下地はページ送り。見た目が無限スクロール。この順で考えると設計がぶれません。
ブログ記事では、John Mueller氏が作った「ページ分割つきの無限スクロール」のデモにも触れています。そこで示されている要点のうち2つが、上の表の裏づけです。一つは網羅性で、すべての項目にアクセスできること。もう一つは重複の無さで、各項目がページの連なりの中で1回だけ載ることです。
代表URLの行には補足があります。公式ドキュメントが避けるよう書いているのは「1ページ目を代表URLにする」ことです。そのため、各ページの canonical はそのページ自身を指す形にしておくと、この記述と食い違いません。こちらは記述から導いた当社の整理です。
ワークスルー:100件の商品一覧を作り直す
具体例で1回通します。商品が100件ある一覧ページ /items があり、最初に20件を表示し、下までスクロールすると次の20件をJavaScriptで読み込む作りです。検索に出るのは最初の20件の商品だけ、という状況です。
- 今の状態を確かめる。 Chromeの開発者ツールを開き、コマンドメニュー(Macは Cmd+Shift+P、Windowsは Ctrl+Shift+P)で「Disable JavaScript」を実行してから
/itemsを再読み込みします。表示されるのが20件だけで、ページ下部に「次へ」や「2」のリンクも無ければ、続きの80件にたどり着く道はスクロール操作しかありません。 - 区切りを決める。 20件ずつ5ページに分けます。
/items?page=1から/items?page=5までです。商品の並び順は固定し、21件目は必ず2ページ目の先頭、というように、どの商品がどのページに載るかを決めておきます。ここが揺れると、同じ商品が2つのページに載る「重なり」が起きます。 - 各URLをサーバー側で返せるようにする。
/items?page=3に直接アクセスしたら、41〜60件目がHTMLとして返る状態にします。前のページで何をしたかに関係なく、そのURLだけで中身が出ることが条件です。 - ページ送りのリンクを置く。 各ページの一覧の下に、前後のページへのリンクをaタグで置きます。見た目を目立たせたくない場合でも、リンク自体はHTMLに残します。
- スクロール読み込みは、その上に重ねる。 JavaScriptが動く環境では、末尾に近づいたら次のページのURLから中身を取得して一覧に追記し、同時に pushState でアドレスバーを
?page=2に更新します。 - canonical と title を整える。
?page=3の canonical は?page=3自身に、title は「商品一覧(3ページ目)|ショップ名」のように、各ページ似た形にそろえます。 - レンダリング結果で確かめる。 Search Console のURL検査ツールで
/items?page=3を検査し、レンダリング後のHTMLに41〜60件目の商品とページ送りリンクがあるかを見ます。公式ドキュメントも、Googlebotがレンダリング後の中身をどう解釈しているかはリッチリザルトテストやURL検査ツールで確認できる、と案内しています。
手順4と5のHTMLとJavaScriptは、最小限なら次のような形です。
<ul id="item-list">
<!-- 21〜40件目の商品(サーバーが HTML として返す) -->
</ul>
<nav class="pager">
<a href="/items?page=1">前へ</a>
<a href="/items?page=3" id="next-page">次へ</a>
</nav>
const next = document.getElementById('next-page');
const observer = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting || !next) return;
const url = next.getAttribute('href');
const html = await (await fetch(url)).text();
const doc = new DOMParser().parseFromString(html, 'text/html');
document.getElementById('item-list').append(...doc.querySelectorAll('#item-list > li'));
history.pushState({}, '', url);
const newer = doc.getElementById('next-page');
newer ? next.setAttribute('href', newer.getAttribute('href')) : next.remove();
});
observer.observe(document.querySelector('.pager'));
ポイントは、JavaScriptが「次へ」リンクの href を読んで次のページを取りに行く作りにしていることです。リンクが正本、スクロールはその自動実行。こうしておくと、JavaScriptが動かない環境でも、リンクをたどれば5ページすべてに届きます。
この例を表に当てはめると、手順2が「ページ分割」と「重複させない」、手順3が「固有のURL」、手順4が「順にたどれるリンク」、手順5が「URLの更新」に対応します。手順1と7は、その前後の確認です。
よくある誤解と例外
「GoogleはJavaScriptを実行するから、全部拾ってくれる」
半分だけ当たっています。GoogleがJavaScriptをレンダリングするのは公式ドキュメントのとおりです。ただ、同じドキュメントが書いているのは「レンダリング後に見えない中身はインデックスに含められない」という側の話でもあります。スクロールをきっかけに読み込む中身が、レンダリング後のHTMLに入っているかどうか。そこは実装しだいで、確かめるまで分かりません。だから手順7の確認が要ります。
「rel="next" と rel="prev" を入れておけば大丈夫」
公式のページ分割ドキュメントは、<link rel="next"> と <link rel="prev"> について「以前は」前後のページの関係を見分けるのに使っていた、という書き方をしています。これに頼るより、本文中に前後ページへのaタグを置くほうが、上で見たリンクの案内に沿った形です。
「もっと見る」ボタンも同じ
スクロールではなくボタンで追加読み込みする一覧も、考え方は同じです。ボタンが button タグとクリックイベントだけでできていると、次のページへのリンクがHTMLにありません。上のワークスルーの「次へ」リンクを、そのまま「もっと見る」の見た目にすれば両立できます。
絞り込み・並べ替えのURL
並べ替え(?sort=price)や絞り込み(?color=red)でも、無限スクロールの一覧はよく使われます。公式ドキュメントは、同じ一覧の絞り込み版・並べ替え版をインデックスさせないよう書いています。ページ分割のURLを用意するのは、基本の並び順の一覧に絞るのが無難です。
自社サイトの自己点検表
最後に、手元の一覧ページに当てはめるための表です。1つでも「いいえ」があれば、ワークスルーの対応する手順に戻ってください。
| 確認項目 | 確かめ方 | 「いいえ」のとき |
|---|---|---|
| JavaScriptを止めても、続きのページへのリンクが見えるか | 開発者ツールで JavaScript を無効にして再読み込み | 手順4 |
?page=3 などを直接開くと、そのページの中身が出るか | 新しいタブでURLを直接入力 | 手順3 |
| 1つの項目が、1つのページにだけ載っているか | 隣り合うページの先頭と末尾を見比べる | 手順2 |
| スクロールに合わせてアドレスバーのURLが変わるか | 実際にスクロールしてアドレスバーを見る | 手順5 |
| 2ページ目以降の canonical が1ページ目を向いていないか | ページのソースで rel="canonical" を探す | 手順6 |
| レンダリング後のHTMLに、そのページの項目が入っているか | URL検査ツールで検査 | 手順3〜5を見直す |
一覧ページが多いサイトでは、この点検を手作業で全部回すのは現実的ではありません。Wemiroのようにサイト内のリンク構造やインデックス状況をデータで追えるツールを使うと、無限スクロールの導入後に「内部リンクからたどれなくなった下層ページ」に気づきやすくなります。
ページ送りそのものの設計(2ページ目に noindex を付けない理由など)は ページネーションの2ページ目にnoindexは不要 で、リンクの書き方の点検は ボタンやJavaScriptのリンクがクロールされない原因と直し方 で詳しく扱っています。一覧の画像を遅延読み込みしている場合は 画像の遅延読み込みは検索に不利? もあわせて確認してください。
出典
