implement prerendering

created at: Jan.30.2020updated at: Jan.30.2020

puppeteer で prerendering の実装

このブログ兼 portfolio サイトを prerendering で実装しました。
prerendering 実装したのは初めてだったので備忘録としても残しておきます。

prerendering とは

prerendering とは、web アプリケーションのレンダリングの一つの方法です。
通常、Reactなどで作られたアプリケーションでは、コンテンツがない空の HTML をサーバーから受け取り、その後JSを読み込んでコンテンツを表示します。
この prerendering では、アプリケーションを build 時にレンダリングしてコンテンツ入りの HTML を作っておきます。クライアントはその build 済みの HTML をサーバーから受け取ることのみでページを表示する事ができます。
そうする事で、TTFB を早めることや、Google などの bot に対して確実にコンテンツ入りの HTML を返せる事などができます。

また、GatsbyJS などはこの prerendering の技術をとりいれています。

今回はこの prerendering を行なってコンテンツ入りの HTML を表示したのちに JS を読み込み、通常のSPAのアプリとして動作するような実装をしました。

使った技術

使用した技術は次のようになります。
react, styled-components, puppeteer, webpack, express, npm script

react prerender などで調べると react-snap を使用した例が多く出てくるため最初はこれで作ろうと思いましたが、create-react-app で作られたアプリだと zero-config で prerendering できるのですが、create-react-app を使用していないため簡単に適用できませんでした。
そのため react-snap の内部でも使用している puppeteer を用いて実装しました。

実装

実装は npm script で command を作成し、そのコマンド一つで prerendering された html ファイルを作成できるようにしました。

ディレクトリの構成はこのような感じです。

clients/
  src/
  dist/
  webpack.config.js
scripts/
prerender/

clients 配下は通常の React Application になっていて、scripts 内に npm script で実行するファイルを保存、prerender 内に prerendering 後の html ファイルを保存しています。

prerendering された HTML を作成する script は以下のような内容になってます:

  1. webpack で client/src 配下 (react) を build した結果を dist 配下に配置 (html と js)
  2. express のサーバーを立ち上げて client/src ディレクトリをサーブする
  3. cloud SQL から公開されているブログの一覧を持ってくる
  4. puppeteer を起動し、先程起動した express サーバーに対して取得したブログのパスでアクセスする
  5. アクセスしたページの html を取得し、html ファイルを作って prerender 配下に置く
  6. 4,5 をブログの数だけループ

また、puppeteer ではスクリーンショットを撮ることが可能なため、各ページのスクリーンショットを作成し、OGP用のイメージとして保存しています。

styled-components の style が取得できない

styled-components ではスタイルを head タグ内に style タグを作成しその中にスタイルを定義しているのですが、パフォーマンス向上のため CSSOM を直接 DOM に読み込ませるということをしています。
そのため document.documentElement.innerHTML などの関数では style タグ内を取得できず、chrome dev tool の element tab などでも style タグ内を確認することができません。

そのままでは puppeteer でも同様に style タグの値を取得できないのですが、最近リリースされた styled-components v5 で導入された disableCSSOMInjection を使用することで回避することができました。

これを使用することで CSSOM をそのまま挿入するのではなく、テキストベースの CSS を DOM に追加する処理に変わるため、puppeteer で style の情報を取得しスタイル込みの HTML を作成できました。

結果

chrome でページソースを見てみると、ちゃんとレンダリングされてることがわかります。

before

before
before

after

after
after

最後に

今回 prerendering した HTML は skelton などではなく完全なコンテンツ入りのHTMLなので、単純な HTML のみで SPA にしない方がパフォーマンスいいかもしれないのでそれも試してみたい。