SEO対策をサーバーサイドレンダリングせずにやっていく


2019/05/08 追記

【大ニュース】
なんとGoogleBotのJSレンダリングエンジンがChrome74相当になって、ES6に対応するようです...!
ここで書かれた内容の多くはすでに古くなっているかもしれません...!
https://webmasters.googleblog.com/2019/05/the-new-evergreen-googlebot.html


もう時は2018年の後半となり、React・Vue・AngularといったモダンJavaScriptフレームワークは開発現場でよく見かける存在となりました。
一方で、依然としてこれらを利用して開発をする場合には「Googlebotがそれらをレンダリングできない可能性がある」という問題がついてまわります。

そのため、サーバーサイド側で前もってJavaScriptをレンダリングしておく、サーバーサイドレンダリングという技術が必須となりました。
しかし、サーバーサイドレンダリングを導入してしまうと、開発工数が膨らみアーキテクチャを複雑にさせてしまう可能性があります。

SEO対策をしたいだけならば、本当に「サーバーサイドレンダリング」をする必要があるのでしょうか。

結論から言いますと、それは不要1といえるかもしれません。
※きちんと「Googlebotにインデックスをしてもらう」ということだけをSEO対策とするのならば、という条件付きです。

というのも、サーバーサイドレンダリングをせずにも、
モダンなJavaScriptで書かれたコンテンツをGooglebotが読める形式にすることができるからです。

GooglebotはChrome 41相当の機能しか持っていない

GooglebotはJavaScriptを実行できるだとかできないだとか、様々な噂がされております。
実際のところは本当にJavaScriptを実行できるのでしょうか、実行できるとしてどの程度までの機能を実行できるのでしょうか。

この疑問に対する答えをGoogleは公開をしています。

以下は「Google 検索」の公式ガイドにある「Google 検索でのレンダリング」からの引用です。

Googlebot では、Chrome 41(M41)に基づくウェブ レンダリング サービス(WRS)が使用されます。通常、WRS では使用する Chrome バージョンと同じウェブ プラットフォームの機能がサポートされます。

ガイドによれば、GooglebotはJavaScriptをレンダリングしていることがわかります。
しかし、「Chrome 41」相当の機能のみがサポートされているとのことです。

2018/09/03現在のChromeのバージョンは「68.0.3440.106」であり、大きなバージョンの乖離があることがわかります。

Chrome 41の機能

Chrome 41の機能とはどういったものだったのでしょうか。
これは「Can I use?」で確認することができます。

実際に最新のChromeとGooglebotが基準としているChromeの機能の違いを見てみましょう。

Can I use?のChrome41 / 68の比較を見てみると、やはり機能に大きな乖離があります。
その中でも特に気になるポイントを以下に引用しました。


左がChrome 41で、右がChrome 68です。
Can I use?のChrome41 / 68の比較より引用

なんとGooglebotは、ES6以降では当たり前に使われている「Arrow functions」「ES6 classes」などのシンタックスを実行できないのです。

実際にGooglebotが来たらどうなるの?

Chrome 41では正常に表示されないJavaScriptで作られたページは、Googlebotからどういう風に見えているのでしょうか。

Google Search Consoleには、Googlebotからページがどういう風にレンダリングされるのかを確認する機能があります。

その機能を利用して、Googlebot対策前と対策後で前後比較してみます。
対象サイトは筆者のサイトである「 https://otakumesi.io 」です。
このサイトはReact + TypeScriptで書かれたシングルページアプリケーション(以下、SPA)で、github.ioでホスティングしているため、サーバーサイドレンダリングをしておりません。2

Googlebot対策前 対策後

Googlebot対策前は真っ白だったSPAが、対策をするとちゃんとレンダリングされるようになりました。
つまり、SPAのコンテンツをGooglebotに認識してもらうには特別な対策が必要になるわけです。


以下に確認をする手順を軽くまとめます。

すると、この節のはじめにあるスクショと同様のレンダリング結果ページを確認できます。

どうすればGooglebotがインデックスできるようになるのか

結論としてはしっかりとレガシーブラウザ対策をしていく必要があるということです。

先に述べたとおり、Chrome 41はレガシーブラウザに毛が生えた程度の機能しか持っておりません。
そのため、それと同等のGooglebotで動くようにするためには、きちんとpolyfillを利用することで機能が足りなくても動くようにする必要があります。

Polyfill とは、最近の機能をサポートしていない古いブラウザで、その機能を使えるようにするためのコードのです。大抵は Web 上の JavaScript です。
MDNより引用

モダンなフロントエンドフレームワークを利用して開発をしている皆さんは、きっとwebpackなどのモジュールバンドラを利用してブラウザで動くJavaScriptにトランスパイルしているかと思います。
大抵のモジュールバンドラにはpolyfillをいれることで、古いブラウザ向けにトランスパイルをしてくれる機能・プラグイン・ライブラリがあるはずです。
それらを利用することで、簡単にレガシーブラウザ対応をすることができます。

以下にbabel 7.0.0を利用した例を示します。

babel 7.0.0を利用した例

以下に@babel/core@babel/preset-envを利用した場合のサンプルを示します。
(babelの設定ですが、少なくともwebpackであればこの.babelrcでそのまま動くと思います。)

まず、@babel/preset-envでES6をトランスパイルできていることが前提になります。3
この方法については省かせていただきますが、公式ドキュメントが詳しいです。

はじめにpolyfillのパッケージをinstallします。4

npm install --save @babel/polyfill # babel v7.0.0以前では babel-polyfill というパッケージ名なので注意

次に.babelrcを下記の通りにすると完了です。

.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage", // @babel/polyfillが必要な箇所でよしなに使うという設定
        "targets": {            // どのターゲットに対してトランスパイルするのかという設定
          "browsers": [
            "> 0.25%",
            "Chrome >= 41",     // ここでGooglebot対応
            "ie >= 11"          // polyfillでレガシー対応するのであれば、ここまで振り切っても良い気がする
          ]
        }
      }
    ]
  ]
}

@babel/preset-envを利用している場合、useBuiltInsという設定をするとpolyfillが適用されるようになります。
この設定にはentryusageという2つの方式が存在しています。
usageとはトランスパイル時に必要な箇所でよしなにpolyfillを挿入してくれるという機能です。
(実験中の機能らしく稀に想定どおりに動かないときがあります。)
詳しくは公式ドキュメントをご参照ください。

また、トランスパイル時にpolyfillが適用するようにした場合には、どのバージョンをターゲットにするか指定する必要があります。
その指定にtargetsを使います。5
例では、browsersを指定しており、これはbrowserslistの記法で対応したいブラウザを配列で設定できるというものです。
この設定では「マーケットシェアが0.25%以上のブラウザ(とそのバージョン)と、Chrome 41以上、IE 11以上を対象にする」としました。

そして、polyfillが動く状態でtargetsにブラウザーとそのバージョンを指定することで、babelがその環境で動かせるJavaScriptにトランスパイルしてくれる、という設定です。


  1. ちなみに、この方法ではOGPなどは対応できないです。 

  2. ソースコードはこちら 

  3. TypeScriptの例が見たい場合は上記のソースコードのwebpackのconfigや.babelrcを参考にしていただければと思います。 

  4. @babel/polyfillで大抵の機能はカバーできますが、fetchIntlなどの特殊なブラウザAPIを利用するためには、別途専用のpolyfillを入れる必要があります。 

  5. targetsについて、詳しく知りたい方は公式ドキュメントをご参照ください。