Rendora - JavaScriptでコンテンツを生成するWebサイトのSEOを改善するダイナミックレンダラーを試してみた

先日ダイナミックレンダリングの仕組みを簡単に導入できるRendoraというツールがリリースされていたので試してみました。

**Rendora**  
https://rendora.co/

# ダイナミックレンダリングとは？

ダイナミックレンダリングとは、特定のユーザーエージェントに対して、クライアント側でレンダリングされるコンテンツとプリレンダリング(事前レンダリング)したコンテンツを切り替えることを指します。

React、Vue、Angularなどの最新のJavaScriptフレームワークで書かれたWebサイトのSEOを向上させることを目的としています。

**Googleのダイナミックレンダリングに関する記事**  
https://developers.google.com/search/docs/guides/dynamic-rendering

**Bingのダイナミックレンダリングに関する記事**  
https://blogs.bing.com/webmaster/october-2018/bingbot-Series-JavaScript,-Dynamic-Rendering,-and-Cloaking-Oh-My


# Rendoraとは？

Rendoraはフロントエンド、バックエンドのコードを変更することなく、
ダイナミックレンダリングの仕組みを導入できるGolang製のプログラム(ダイナミックレンダラー)です。

Rendoraはバックエンドの前でリバースHTTPプロキシとして機能します。
Google Bot等のWebクローラー（または指定したユーザーエージェント）からのリクエストを検出し、ヘッドレスChromeでレンダリングを行い、
そのレンダリングしたHTMLをクライアント(Webクローラー)に返します。

![RendoraDiagram.png](https://qiita-image-store.s3.amazonaws.com/0/24450/9222a171-d463-df58-c8f0-5b4c35d75ac3.png)


Rendoraと似たようなサービスやツールには、[Prerender.io](https://prerender.io/)や[Rendertron](https://github.com/GoogleChrome/rendertron)があります。

その他、Rendoraに関する情報を知りたい方は[Rendora公式サイト](https://rendora.co/)をご参照ください。

# Rendoraを試す

Rendoraの動きを確認するため、簡単なプロジェクトを作成します。

今回はMac上でRendoraを動かすことに挑戦してみました。

## 検証環境

* Mac mini (2018) / 16GBメモリ / 3.2GHz Intel Core i7
* macOS Mojave 10.14.1
* Google Chrome 71.0.3578.80 (64ビット)
* node v10.14.1
* npm 6.4.1
* go 1.11.2 darwin/amd64

## 作業用ディレクトリ作成

作業用のディレクトリを作成して、その中でコードを書いていきます。

```bash:~/
# 作業用ディレクトリ作成
mkdir rendora-example && cd $_
```

## Vue.js プロジェクトの作成

バックエンドおよびフロントエンドのコードが必要になるので、手っ取り早くVue CLIでVueプロジェクトを作ります。

```bash:~/rendora-example/
npm install -g @vue/cli
vue create client
#=> default を選択
cd client
npm run serve
```

ブラウザで http://localhost:8080/ にアクセスします。

![vue_example.png](https://qiita-image-store.s3.amazonaws.com/0/24450/27392548-0563-84e3-ac4f-0b98acd9d267.png)

ソースコードを確認します。

![vue_example_source.png](https://qiita-image-store.s3.amazonaws.com/0/24450/1a7158bb-5a1c-26b9-30a0-dcad603fc110.png)


これがクライアント側に返されたHTMLになります。

当然ですが、JavaScriptでレンダリングされるコンテンツはHTMLに含まれておりません。

そのためWebクローラーがアクセスするとコンテンツが存在しないかのように見えます。

そこで、Rendora(とヘッドレスChrome)を使ってダイナミックレンダリングを行います。

## Rendoraのビルド

先ほど `npm run serve` 動いているVueアプリケーションはそのままにしておき、別のターミナルウィンドウを開いて、Rendoraのセットアップを行なっていきます。

Rendoraのバイナリがまだ配布されていないようなので、
今回はRendoraをビルドしてバイナリを生成します。

RendoraはGo言語で書かれています。ビルド環境を整えるために、今回は[Homebrew](https://brew.sh/)を使ってGo言語をインストールします。

```bash:~/
brew install golang
```

Rendoraのソースコードをgit cloneし、ビルドを行います。

```bash:~/
# 別ターミナルウィンドウで
cd rendora-example
git clone https://github.com/rendora/rendora
cd rendora/rendora
```

```bash:~/rendora-exaple/rendora/rendora
go build
```

```
/usr/local/include/Block.h:16:3: error: Never include this file directly. Use <lzma.h> instead.
#       error Never include this file directly. Use <lzma.h> instead.
        ^
1 error generated.
```

おっと、エラーが発生しました。

Google先生に助けを求めます。

> "I found a workaround by renaming /usr/local/include to /usr/local/include_old (well, name does not really matter, 
> just the fact that g++/clang will not search for headers in this folder anymore). "
> 
> *g++/clang is looking for headers for some reason in this path, 
> This happens when you migrate from one Mac to another and/or upgrade to HighSierra.

https://github.com/rbenv/ruby-build/issues/1169


macOSの移行アシスタントを使ってアカウントを移行すると発生することがあるみたいです。

エラーを回避するには`/usr/local/include`を`/usr/local/include_old`にリネームすると良いとのことでした。

High Sierra以降の方は以下のコマンドを実行します。

```bash
sudo mv /usr/local/include /usr/local/include_old
```

そして、もう一度Rendoraをビルドします。

```bash:~/rendora-example/rendora/rendora
go build
```

今度はビルドに成功しました。

`rendora` というバイナリが生成されているので、実行してみます。

```bash:~/rendora-eample/rendora/rendora
./rendora -h
#=> Usage of ./rendora:
#=>   -c, --config string   set config file
#=> pflag: help requested
```

Usageが表示されました！正しくビルドできたようです。

## Rendoraの設定ファイル作成

続いて、Rendoraの設定ファイルであるconfig.yamlファイルを作成します。

```bash:~/rendora-example/rendora/rendora
cd ../
```

```bash:~/rendora-example/rendora
touch config.yaml
vim config.yaml
```

```yaml:~/rendora-example/rendora/config.yaml
debug: true
listen:
    address: 0.0.0.0
    port: 3001

target:
    url: "http://127.0.0.1"

backend:
    url: "http://127.0.0.1:8080"

headless:
    waitAfterDOMLoad: 0
    internal:
        url: http://127.0.0.1:9222

output:
    minify: false

filters:
    userAgent:
        defaultPolicy: blacklist
        exceptions:
            keywords:
                # NOTE: キーワードは小文字にすること(大文字だとマッチしない)
                - bingbot
                - yandex
                - curl

server:
    enable: true
```

設定ファイルを作成はこれだけです。
いよいよRendoraを動かしますが、ヘッドレスChromeが必要となるため、事前に起動しておきます。
　
## ヘッドレスChromeの起動

別のターミナルウィンドウを起動して、ヘッドレスChromeを起動します。

```bash
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --headless --remote-debugging-port=9222
```

## Rendoraの起動

バックエンドのサーバー(Vueアプリケーション)とヘッドレスChromeが起動している状態で、Rendoraを起動します。

```bash:~/rendora-example/rendora
./rendora/rendora -c ./config.yaml
```

## 動作確認

別ターミナルを開き、curlを使ってHTMLを取得します。

```bash
# curlの実行
# NOTE: URL末尾の`?$(date +%s)`はレスポンスをキャッシュさせないためのものです
curl "http://localhost:3001/?$(date +%s)"
```

すると以下のレスポンスが返ってきます。

```html:response
<!DOCTYPE html><html lang="en"><head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="/favicon.ico">
    <title>client</title>
  <link href="/app.js" rel="preload" as="script"><style type="text/css">
h3[data-v-469af010] {
  margin: 40px 0 0;
}
ul[data-v-469af010] {
  list-style-type: none;
  padding: 0;
}
li[data-v-469af010] {
  display: inline-block;
  margin: 0 10px;
}
a[data-v-469af010] {
  color: #42b983;
}
</style><style type="text/css">
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style></head>
  <body>
    <noscript>
      <strong>We're sorry but client doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"><img alt="Vue logo" src="/img/logo.82b9c7a5.png"><div data-v-469af010="" class="hello"><h1 data-v-469af010="">Welcome to Your Vue.js App</h1><p data-v-469af010="">
    For a guide and recipes on how to configure / customize this project,<br data-v-469af010="">
    check out the
    <a data-v-469af010="" href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
  </p><h3 data-v-469af010="">Installed CLI Plugins</h3><ul data-v-469af010=""><li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li></ul><h3 data-v-469af010="">Essential Links</h3><ul data-v-469af010=""><li data-v-469af010=""><a data-v-469af010="" href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li></ul><h3 data-v-469af010="">Ecosystem</h3><ul data-v-469af010=""><li data-v-469af010=""><a data-v-469af010="" href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li><li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li></ul></div></div>
    <!-- built files will be auto injected -->
  <script type="text/javascript" src="/app.js"></script>

</body></html>
```

プリレンダリングされたHTMLが返ってきました！感激！

# 所感

クライアントとサーバーの間にRendoraを入れるだけで簡単にダイナミックレンダリングの仕組みが導入できました。

Rendoraはまだリリースされたばかりなので、機能的に足りていない機能やブラウザで開くとリソースの取得が完了しない等の不安定な部分もあります。

本番環境で使うにはまだ早いですが、ダイナミックレンダリングの仕組みを知るということにおいて、参考となるプロジェクトです。

今後が楽しみですね！

興味を持った方はRendoraを試してみてください！