# CSR Dynamic Rendering

CSR Dynamic Renderingとは、クローラが来たときだけサーバレンダリングを実行するアーキテクチャです。CSR Dynamic Renderingの目的は、SEOを最適化することです。CSRのデメリットの章でも見た通り、CSRはSEOで不利になる可能性があります。CSRは、クライアント側でコンテンツを生成するため、クローラがコンテンツを解析するまでタイムラグが発生してしまうからです。

その課題を解決するためには、SSRやSSGに切り替えるのが有効的です。しかし、アプリケーションの規模や開発リソースの関係で、SSRやSSGへの移行が難しいこともあるでしょう。

CSR Dynamic Renderingは、既存のCSRのアーキテクチャを変えずに、クローラが来たときだけサーバサイドレンダリングを実行することができます。そのため、導入コストが低く、SEOの最適化がしやすいというメリットがあります。Googleでは、サーバサイドレンダリングへの移行を推奨 (opens new window)していますが、とりあえずSEOだけ最適化したいとニーズには有効な選択肢になります。

# CSR Dynamic Renderingの仕組み

CSR Dynamic Renderingは、サーバ側でクローラが来たときだけコンテンツをレンダリングするパターンです。通常のアクセスの場合は、CSR同様に静的ファイルを配信します。レンダリング方法は、自前でサーバにレンダリングする処理を実装するか、Googleのrendertron (opens new window)prerender.io (opens new window)などのサービスを使うことができます。今回は、ローカル環境でrendertronを試したいと思います。

TIP

Googleのrendertronは、現在、Dynamic Renderingを推奨していませんが、レンダリングの仕組みを理解するため使用します。

Please note that this project is deprecated. Dynamic rendering is not a recommended approach and there are better approaches to rendering on the web.

# rendertron

はじめに、rendertronの仕組みを見てみましょう。

rendertronは、Googleで開発されたDynamic Renderingをするためのツールです。内部でPuppeteerを使用して、該当のサーバへアクセスします。そして、取得したHTMLをクローラ用にシリアライズして返却します。既存のアプリケーションの構造を変えずにCSRをレンダリングできるため、コストをかけずにSEO対策したい場合は、有効な選択肢です。

CSR-Prerendering

rendertronサーバに対して、GET /render/<該当のURL>の形式でアクセスすると、該当のURLのHTMLを取得してレンダリングを実行します。例えば、google.comにアクセスする場合は、次のように指定します。

http://localhost:3000/render/https://google.com

# 実装

では、rendertronを使って、CSR Dynamic Renderingの実装をしましょう。今回は、Vite + ReactのSPAアプリケーションを構築します。このアプリケーションは、HomeAboutBlogContactページで構成されます。

CSR Prerenderingの実装

Blogページでは、サンプルのAPIからデータを取得して、リストを表示しています。非同期処理を伴うコンテンツも、rendertronでサーバレンダリングして、SEOの最適化をしたいと思います。

CSR-Prerendering-サーバサイド-3.png

サーバ構成は次の通りです。

CSR-Prerendering-1.jpg

静的サーバがhttp://localhost:3001からSPAの静的ファイルを配信します。静的サーバの前段で、Proxyサーバを設置します。Proxyサーバでクローラの判定をし、クローラが来たときは、rendertronサーバへ遷移し、サーバレンダリングを実行します。rendertronは、静的サーバにアクセスし、コンテンツを取得します。取得したコンテンツの中身をクローラ用に書き換えてから、Proxyサーバへ戻します。

上記をまとめると、クローラが来たときの流れは以下の通りです。

  1. Proxyサーバでクローラを判定
  2. Proxyサーバからrendertronサーバへ遷移
  3. rendertronサーバが静的サーバへコンテンツを取得
  4. 取得したコンテンツの中身をクローラ様に書き換え、Proxyサーバへ返す
  5. Proxyサーバからクライアントへ返す

まずは、rendertronをローカル環境で動かしてみましょう。GitHub (opens new window)からソースをダウンロードして、ローカルで実行します。

$ git clone https://github.com/GoogleChrome/rendertron.git
$ cd rendertron
$ yarn
$ yarn build

TIP

すでにメンテナンスがされていないためか、TypeScriptで型エラーになります。自身の環境で修正してから起動してください。

サーバを立ち上げると、http://localhost:3000でrendertronが起動します。

$ yarn start

$ node build/rendertron.js
Listening on port 3000

続いて、Vite + ReactのSPAアプリケーションを実装します。フォルダ構成は次の通りで、http://localhost:3001で動作するように設定しています。

spa/
├── index.html
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.tsx
│ ├── assets
│ │ └── react.svg
│ ├── components
│ │ └── Navigation
│ ├── index.css
│ ├── main.tsx
│ ├── pages
│ │ ├── About
│ │ ├── Blog
│ │ ├── Contact
│ │ └── Home
│ ├── routes.tsx
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock

yarn devで開発サーバを立ち上げておきましょう。

$ yarn dev

$ vite --port 3001

  VITE v4.3.3  ready in 319 ms

  ➜  Local:   http://127.0.0.1:3001/
  ➜  Network: use --host to expose
  ➜  press h to show help

最後に、Proxyサーバを実装します。Proxyサーバは、DockerのNginxを使用します。フォルダ構成は次の通りです。

proxy/
├── default.conf
└── docker-compose.yml

docker-compose.ymlを作成して、イメージにnginxを指定します。

version: '3'

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - "7000:7000"
    volumes:
      - ./default.conf:/etc/nginx/conf.d/default.conf

次に、default.confを作成し、Nginxの設定をします。

upstream rendertron {
   server host.docker.internal:3000;
}

upstream static {
   server host.docker.internal:3001;
}

map $http_user_agent $is_bot {
  # テストのため、常にクローラをONにする
  default 1;
#   '~*googlebot' 1;
}

server {
   listen 7000;

   # クローラがきたときは、 http://rendertron/ へ遷移する
   if ($is_bot = 1) {
     rewrite ^(.*)$ /rendertron/$1;
   }

   location /rendertron/ {
     proxy_set_header X-Real-IP  $remote_addr;
     proxy_set_header X-Forwarded-For $remote_addr;
     proxy_pass http://rendertron/render/$scheme://localhost:3001$request_uri;
   }

   # 通常のユーザの場合は、静的ファイルを配信する
   location / {
      proxy_pass  http://static/;
   }
}

クローラが来たときと通常のユーザが来たときで、リバースプロキシの設定をしています。クローラが来たときは、http://localhost:3000/ へ遷移し、通常のユーザの場合は、http://localhsot:3001へ遷移します。今回はテストのために、常にクローラが来るように設定しています。

それでは、Dockerコンテナを立ち上げてみましょう。

$ docker compose up

Proxyサーバは、http://localhost:7000でアクセスできます。試しに、http://localhost:7000/blog にアクセスしてみましょう。

ページのリソースを見てみると、サンプルAPIのデータが初回のHTMLに含まれているのが分かります。

CSR-Prerendering-サーバサイド-4.png

このようにrendertronを挟むことで、CSRのサイトでも、SSRのようにレンダリングすることができました。クローラのときだけサーバレンダリングを実行するので、ユーザーへの影響がほとんどなく実装することができます。ただ、rendertronは現在メンテナンスされていない状況です。他の選択肢として、prerender.ioなどのサービスを使うと、同じようなアーキテクチャを実現できます。

今回はローカル環境で実装しましたが、本番運用したい場合も同じような構成で実現可能です。例えば、AWSでCloudfrontを使っている場合は、Lamda@edge (opens new window)を挟むことでクローラの判定とrendertronの実装が可能でしょう。同様に、Cloudflare Workerを使って、クローラの判定とprerender.ioのサービスを繋げるような構成でも可能 (opens new window)でしょう。

間にレンダリングを挟むため、レイテンシーが発生するデメリットはありますが、導入コストが低く、アプリケーションレイヤーを変えることなく実装できるのが大きなメリットと言えるでしょう。