🚀

Next.js は本当にSEOに強いのか調べてみた

2022/01/23に公開

Next.js って SEO に強いっていうけど、本当はどーなのよ

業務でブログサイトをリプレイスすることとなり、Next.js は SEO に強いと聞いたけど、どんな感じかなと思って調べてみました。

調べる前の私の知識は「SSG(もしくはISR)により、事前に静的なファイル(HTML)が作成されるため、ハイパフォーマンスなサイトができる」というものでした。
そこで、公式サイトで紹介されている SSG で構築された DEMO サイトを試しに見てみました。
https://demo.vercel.store

確かに表示は速いですし、遷移もサクサクと動きます。でも、あれ?この遷移ってブラウザによる遷移ではなく、SPA のように JS に描写で切り替えてね?と思ったのが、最初です。
それって SEO 的にいいんだっけと。

気になったので、調べてみることにしました。

Next.js がハイパフォーマンスなサイトを作るために提供している機能を2つあげます。

  1. Pre-rendering
  2. code splitting

です。

Pre-rendering

Pre-rendering は SSG と SSR がありますが、Next.js は SSG を推奨しています。これは事前に静的な HTML を作成しておく方法で、サイトにアクセスされた時、まず、そのHTMLが返されます。HTML がブラウザに表示されると、JS の読み込みが走り、一部の React コンポーネントが有効となります。その React コンポーネントの一例が <Link> となります。

ここでは <Link> がどのようなことをしているか見ていきます。

<Link> の使い方は簡単で、<a> タグをラッパーするように使います。(なぜ <a> タグが必要なのかは後述します)

import Link from 'next/link'

function Foo() {
  return (
    <Link href="/foo">
      <a>foo</a>
    </Link>
  )
}

export default Foo

<Link> を使っていると、Pre-rendering で事前に作成された HTML でも、遷移時は JS による画面切り替えを実施してくれます。これは遷移先の HTML 全体をサーバーから取得する必要がないため、高速に遷移できます。また 、Next.js では production 環境の場合のみ、<Link> で指定されているページはバックグランドで事前取得されるため、より高速に遷移できます。これを prefetch と呼びます。

この prefetch 機能は素晴らしいですが、何でもかんでも prefetch されると不要なページも事前に読み込まれて過負荷に繋がります。その回避策として <Link> には prefetch 条件を指定できます。デフォルトでは true になっており、これは 「そのリンクが viewport に入ったら」バックグランドでリンク先のページを取得します。false にした場合は、「そのリンクにマウスが乗った場合(on hover)」のみとなります。ここはアクセスされやすいページなどで上手く切り分ける必要がありますね。

少し話を戻しますが、
これが、最初に疑問に思った JS で遷移している理由です。では、SEO 上問題ないのでしょうか。

SEO については、以前に社内のチームメンバーがクローラーについて纏めた社内 wiki が役に立ちました。(社内 wiki なので、ここでは、その wiki で参照されている google 公式アナウンスから抜粋します)

まず、「Googlebot では検索用にページをレンダリングする際に、最新の Chromium レンダリング エンジン(この投稿の時点でバージョン 74)を実行するようになりました。今後、Googlebot のレンダリング エンジンは定期的に更新され、最新のウェブ プラットフォームの機能をサポートできるようになります。」とのこです。つまり JSで遷移されていても、 Chrome で表示されていれば、Google Bot は正しく認識してくれます。
引用: https://developers.google.com/search/blog/2019/05/the-new-evergreen-googlebot

また、「Google のクローラがたどれるリンクは、href 属性が指定された <a> タグのみです」とのこと
引用: https://developers.google.com/search/docs/advanced/guidelines/links-crawlable?hl=ja
ここが、<Link><a> タグが必要な理由です。<Link> は Pre-rendering 時に、<a> タグに変換されます。

<Link href="/foo">
  <a>foo</a>
</Link>

=> <a href="/foo">foo</a>

これにより、Googlebot は問題なくサイト内リンクをクロールしてくれます。

試しに、先ほどの DEMO サイトをブラウザ側で JS を無効にして、アクセスしても問題なく遷移できます。もちろん、 <Link>(JS) は無効になっているので、純粋な <a> タグとして、事前作成されているHTMLに遷移します。

ただ、 基本は、JS で画面描写されるのであれば、事前に HTML 作成する意味はあまりないのではと思われる方もいるかもしれません。

これはランディングページ用に作成しているのだと思います。例えば、トップページのみHTMLを作成していた場合、SNS のシェア機能だったり、google 検索からのアクセスで、トップページ以外に直接アクセスされた場合、まずは、トップページの HTML を読み込み、その時に JS で対象ページを描写するという迂回した表示方法となってしまいます。

つまり、Nex.js は ランディングページ用に全ページの HTML を事前に作成している。遷移時は、その HTML は使わずに JS により描写切り替えで、高速な遷移を実現している。これは Googlebot も認識できる方法のため、SEO上問題ない(むしろ、高速なので、CWV的には有利)となります。

code splitting

これは、主に SPA の課題だった問題を解決したものとなります。SPA(Single Page Application) はその名の通り、全てのページを JS で描写するため、他のページのスクリプトも最初に読み込んでおく必要がありました。つまり、初期ロード時に不要なスクリプトも読み込むので、必然的にユーザー体験を悪くします。また、ページが増えれば増えるほど、不要なスクリプトが増えるため、遅くなります。

これを解消するために、Next.js では「そのページに必要なスクリプト」しか取得しないようになっています。このページ単位でのスクリプト分割は自動でやってくれますが、一点、注意が必要です。Next.js では全ページで読み込まれる _app.js があります。ここに無闇に CSS や JS を import したり、実装を肥大化させてしまうと、それを必要としないページでも読み込まれることになります。 code splitting の効果が薄くなるため、 _app.js の肥大化には注意が必要です。

Next.js は素晴らしい

Pre-rendering の箇所で、 <Link> を説明しましたが、他にも素晴らしいコンポーネントがあります。個人的に良いと思ったのが、<Image> です。

import Image from 'next/image'

<Image
  src="/foo.png"
  width={250}
  height={100}
  alt="Foo Image"
/>

また、以下のように書くと、横幅と高さは自動で設定されます。

import Image from 'next/image'
import foo from '../public/foo.png'

<Image src={foo} alt="Foo Image"/>

<Image> では、そのブラウザに適したサイズを自動で表示してくれます。また、その画像は遅延読み込みされ、viewport に来た時に、読み込まれるようになります。このような、遅延読み込みの場合は CWV の CLS[1] が問題なとなりますが、<Image> は画像が表示される箇所には空白を表示しておくことで、防いでくれます。

つまり、画像読み込みのベストプラクティスみたいな動作を <Image> を使うだけで、実現できます。

Next.js は SEO に強いと聞いたが、どんな感じかなーと調べたのがきっかけですが、個人的には素晴らしいフレームワークと思いました。ただ、prefetch 条件や、 _app.js の肥大化など、実装者が気を付けないといけない箇所があることも分かりました。

Next.jsの力を 100% 発揮されるよう実装し、良いサービスを提供していきましょう!

脚注
  1. Cumulative Layout Shift の略。ページ読み込み中に、レイアウトが動くと、意図しない場所のクリックにつながったりして、UX上良くない。CLSはどれほど動いたのかを表す数値で、CWVの指標の一つ。 ↩︎

Discussion