SPAが検索で苦戦する理由とその直し方。SSR、静的生成、プリレンダリング、React 19やreact-helmet-asyncによるルートごとのタイトル、実URL、ソフト404、サイトマップ、URL検査、ソースチェッカーを紹介します。
シングルページアプリは、検索エンジンにほぼ空のHTMLシェルを送り、あとからJavaScriptでページを組み立てることがよくあります。GoogleはJavaScriptをレンダリングできますが、それは遅れて行われ、ほかのクローラーはレンダリングしない場合があります。サーバーレンダリング、静的生成、プリレンダリングのいずれかでURLごとに実際のHTMLを配信し、すべてのルートに固有のタイトル、説明文、canonicalを設定して解決しましょう。
最も重要な3つの対策
このサイト自体も、すべての公開ページをビルド時にプリレンダリングするReactのシングルページアプリです。つまりここにあるのは、当社が自分のページで実際に行っているチェックです。ページのソースをチェッカーに貼り付けると、JavaScriptが実行される前にクローラーが何を受け取るかを確認できます。
はい。GoogleはJavaScriptをレンダリングしますが、ページはレンダリングキューで待たされ、読み込みに失敗したものは見落とされます。ほかの検索エンジンやリンクプレビュー用のボットはレンダリングが限定的か、まったく行わないため、HTMLを配信するほうが安全です。
いいえ。Next.jsを使うとサーバーレンダリングや静的生成が簡単になりますが、ViteやCreate React App形式のSPAでも、公開ルートをビルド時にプリレンダリングすれば同じようにクロール可能なHTMLを得られます。
基本的なタグには不要です。React 19は、どのコンポーネントからでもtitle、meta、link要素をレンダリングし、headに移動させます。React 18以前ではreact-helmet-asyncが引き続き役立ちます。
クローラーと人が同じコンテンツを受け取る限り、なりません。プリレンダリングは、ユーザーがどのみち見るページを保存しておくだけです。ボットに配信するHTMLの内容が異なる場合にクローキングになります。
クローラーに実際のHTMLを渡す方法
React 19では、どのコンポーネントからでもtitle、meta、linkタグをレンダリングでき、Reactがそれらをドキュメントのheadに移動します。各ルートが自分のタグを設定します。
アプリ全体を一度HelmetProviderで囲み、ページごとにタグを設定します。async版はサーバーレンダリングやプリレンダリングでも安全に使えます。
GoogleはURLを判断する際、#以降をすべて無視します。History API(BrowserRouter、createBrowserRouter)を使い、ビューごとに固有のパスを持たせましょう。
クローラーはhrefを持つa要素をたどります。divに付けたクリックハンドラーはクローラーには見えません。ルーターのLinkコンポーネントは本物のアンカーをレンダリングします。
クライアント専用アプリはどのURLにもステータス200で応答するため、存在しないページが本物のページのように見えてしまいます。not-foundビューにnoindexを付けるか、サーバーまたはプリレンダリングの段階で本物の404を返しましょう。
公開URLをそれぞれ1回ずつ、canonicalのアドレスで記載します。アプリが使うルート一覧と同じものから生成すれば、ずれが生じることはありません。
タグを自動で作成したいなら、こちらをお試しください:
ページソースチェッカー
「ページのソースを表示」のHTMLを貼り付けて、クローラーが受け取る内容を確認します。
ページのソース(例は典型的な空のSPAシェルです)
SPAのSEOチェックリスト
タイトル
「{title}」({n}文字)
HTMLにタイトルがありません
メタディスクリプション
{n}文字
なし
canonicalリンク
メイン見出し(h1)
JavaScript実行前にh1がありません
表示テキスト
JavaScript実行前の本文に{n}語
クロール可能なリンク
実際のhrefを持つa要素が{n}個
Open Graphタイトル(リンクプレビュー)
インデックス許可
robotsメタ:{robots}
robotsメタなし(インデックス可能)
無料で始める · クレジットカード不要