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 検索でのレンダリング](https://developers.google.com/search/docs/guides/rendering)」からの引用です。

> 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?](https://caniuse.com/)」で確認することができます。

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

[Can I use?のChrome41 / 68の比較](https://caniuse.com/#compare=chrome+41,chrome+68)を見てみると、やはり機能に大きな乖離があります。
その中でも特に気になるポイントを以下に引用しました。

> ![Screen Shot 2018-09-05 at 22.54.46.png](https://qiita-image-store.s3.amazonaws.com/0/97363/9383dfdd-0fb1-e315-17aa-d283ec61d72d.png)
> 左がChrome 41で、右がChrome 68です。
> [Can I use?のChrome41 / 68の比較](https://caniuse.com/#compare=chrome+41,chrome+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対策前|対策後|
|---|---|
|![Screen Shot 2018-09-02 at 12.06.15.png](https://qiita-image-store.s3.amazonaws.com/0/97363/350e5803-7d42-5c48-e13b-aa38d71b6c45.png)|![Screen Shot 2018-09-05 at 23.22.20.png](https://qiita-image-store.s3.amazonaws.com/0/97363/fc362d54-5bea-ec4e-3f77-0eb8ebb21e0a.png)|

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

---

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

![Search_Console_-_ダッシュボード_-_https___otakumesi_io_.png](https://qiita-image-store.s3.amazonaws.com/0/97363/b2a0889a-35f6-1dd6-514f-aeb3ac9ed8be.png)

![Search_Console_-_Fetch_as_Google_-_https___otakumesi_io_.png](https://qiita-image-store.s3.amazonaws.com/0/97363/ce5bed1e-20c2-f2d1-78d3-22ae1d0707f1.png)

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

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

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

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

> Polyfill とは、最近の機能をサポートしていない古いブラウザで、その機能を使えるようにするためのコードのです。大抵は Web 上の JavaScript です。
> [MDNより引用](https://developer.mozilla.org/ja/docs/Glossary/Polyfill)

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

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

# babel 7.0.0を利用した例

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

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

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

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

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

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

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

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

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

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

[^2]: ソースコードは[こちら](https://github.com/otakumesi/otakumesi.github.io)

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

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

[^5]: `targets`について、詳しく知りたい方は[公式ドキュメント](https://babeljs.io/docs/en/babel-preset-env#targets)をご参照ください。