BETA

SSR, CSRについてのメモ

投稿日:2019-08-16
最終更新:2019-08-16

CSRとは? SSRとは?

前回の勉強会用メモで書いたように、Universal JSで書かれたコードを「いつ、どこで、どこまでレンダリングするか」

  1. リクエスト時クライアント側すべてレンダリングする(CSR
  2. リクエスト時サーバ側最低限(ファーストビューやSEO用の部分)レンダリングし、それ以降クライアント側でレンダリングする(SSRとCSR
  3. JSのビルド時サーバ側最低限レンダリングをして静的ファイルを生成しておき、リクエスト時はその静的ファイルとその後の動作に必要なJSを返してクライアント側でレンダリングする(プリレンダリングとCSR
手法 実装の容易さ 初期表示 柔軟性 備考
CSR |◯ 初期表示が遅い
SSRとCSR |◯ 実装コストが高い
プリレンダリングとCSR |ユーザごとに表示内容が異なるページには適用不可

SSRのメリット

SEOの観点はもはやSSRのメリットにはならない。

Google I/O 2019 参加レポート 3日目

Google Crawler bot はこれまで少し古いバージョンの Chromium で固定されていました。そのため、SPA だけではインデックスに懐疑的で、SEO が必要なサイトでは SSR をすべき、とされていました。今回の発表でその状況は一変し、SEO が必要という理由で SSR を選択肢に挙げる必要がなくなりました。表示における UX の改善施策としては SSR はこれからも有効な手段であることに変わりありません。

SSRの一番の目的は First View Performance を上げること。ざっくりいうとユーザが最初に目にする初期表示を早くすること。

First View Performanceとは具体的にどういうものか:

Server Side Renderingについて知るべきこと。Server Side Renderingとは何か? それによって何が改善されるのか?(前編) ng-japan 2017

SSRのデメリット

(フレームワークを使わないと)とてもめんどくさい。

SSRの実装の大変さ

実装例:

webpack4でReact16のSSR(サーバサイドレンダリング)をする

React v16 + react-router v4 + ExpressでSSR+SPAする - Qiita

上記の例の中で何をやっているかというと:

React v16でのサーバーサイドレンダリング - blog.koba04.com

v15まで

ReactでSSRしたコンテンツをクライアントでも再利用したい場合、これまではSSRしたHTMLを元に生成されたchecksumとクライアントサイドで構築したReactElementから生成したchecksumが一致する必要がありました。

これはつまり、ReactDOMServer.renderToStringのエントリーポイントと、ReactDOM.renderのエントリーポイントが一致する必要があることを示します。

v16

v16では、checksumによりチェックは行われず、可能な限りすでに構築されているDOMを再利用しようとします。 また、ReactDOM.hydrateという明示的なAPIを使うことで、サーバー側とクライアント側でエントリーポイントを合わせる必要はありません。

要は、SSRしたものをCSR時に重複してレンダリングせずに再利用するのに悪戦苦闘している。
React v16から多少楽にはなっているものの、サーバ側もクライアント側も大変そう。

Next.jsのようなフレームワークを使えば、特に意識しなくてもSSRが簡単にできるよ!!という話。

SSRしたもののレンダリングをCSRでスキップする方法の詳細

エントリポイント JavaScript Primer #jsprimer

エントリポイントとは、アプリケーションの中で一番最初に呼び出される部分のことです。 アプリケーションを作成するにあたり、まずはエントリポイントを用意しなければなりません。

Webアプリケーションにおいては、常にHTMLドキュメントがエントリポイントとなります。

返すhtmlファイルに書かれているDOMが空のdiv要素1つとかなのがCSRで、ファーストビューの分までレンダリングしたhtmlファイルが返るのがSSR。

大まかな流れは以下の通り

  1. サーバで、エントリポイントを起点にコンポーネントをレンダリングする(componentDidMountの前まで)。
  2. 具体的にはReactDOM.renderToNodeStreamを使い、仮想DOMの構築をサーバ側で行い、仮想DOMの構造に合わせてHTMLファイルを出力してからクライアントへ返す。
  3. ブラウザでHTMLファイルとCSSファイルを元にLayoutの処理が行われ、レンダリングツリー(DOMツリー + CSSOMツリー)が構築される。
  4. クライアント側でサーバと同じJSファイルが読み込まれ、ReactDOM.hydrateで仮想DOMが再度生成される。サーバ側で構築された分の仮想DOMはHTMLに反映済みなので、それ以降(componentDidMountから)の部分でイベントハンドラの設定などが適用される。

大事なポイントとして、JS自体は2回実行され、仮想DOMの構築も2回行われる。2回実行されるが、SSRの場合操作対象のDOMツリーが完成した状態で2回目が実行され、仮想DOMとの差分が無いため、Layoutの処理は再度走らず重くならない。

Webエンジニアが知っておくべきブラウザレンダリングの仕組み | tataneのうたたね

↑でいう Scripting の段階でJSファイルが読み込まれ、実行自体はツリー構築後になる。

Next.jsにおけるSSR

Next.jsでは getInitialProps が最初に走る処理であるが、サーバ側ではエントリポイントで呼ばれたときのみ、クライアント側ではhistoryAPIを叩いて遷移するときのみ呼ばれる。

すなわち、仮想DOMの構築は確かに2回行われるが、getInitialPropsでのAPIへのリクエストはちゃんと1回しか行われないようになっている。

また、Reduxで状態管理をしている場合はサーバ側で保存した状態をクライアント側へ引き継ぐ必要がある。

Next.jsを使っている場合 next-redux-wrapper というライブラリでhtmlへ埋め込んでクライアントへ渡したり。

今後のSSR

SSRが短縮するのはFCP(First Content Paint)まで。ユーザからの操作を受け付けるTTI(Time To Interactive)までの時間は短くならない。

そこを、FCPからTTIまでの間に実行されるJSを分割して読み込むようにするのが Progressive Hydration

Rendering on the Web - Web上のレンダリング | Web | Google Developers

Progressive Hydration #react_fukuoka - Speaker Deck

メモ

v15までのSSRの流れ詳細

  1. サーバで、エントリポイントを起点にコンポーネントをレンダリングする(render関数の実行まで)
  2. ReactDOM.renderToStringを使い、レンダリング内容を文字列としてhtmlへ埋め込みクライアントへ渡す。埋め込み時に出力したDOMからchecksumを計算して data-react-checksum 属性として値を埋め込んでおく。
  3. ブラウザでHTMLファイルとCSSファイルを元にLayoutの処理が行われ、レンダリングツリー(DOMツリー + CSSOMツリー)が構築される。
  4. クライアント側でサーバと同じJSファイルが読み込まれ、ReactDOM.renderで仮想DOMが生成される。ReactDOM.render実行時にchecksumを参照して、差分がなければhtmlを更新しない。

checksum比較のために、エントリポイント、すなわちReactDOM.renderToStringを実行する対象とReactDOM.renderを実行する対象を一致させる必要がある

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@comoの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう