Reactのサーバサイドレンダリングとパフォーマンスについてのメモ

Reactのサーバサイドレンダリングとパフォーマンスについて調べてたのでメモ :memo:

## 基本的なこと

### サーバサイドとReactのproduction build

要約: Reactをサーバサイドで使うときも、クライアントサイドのように圧縮(というよりはcode eliminate)しないと遅い

Reactはdev向けのコードを大量に含んでいる。
これは`process.env.NODE_ENV !== 'production'`の時実行されるassertや警告などが主となりproductionには必要ない。
そのため、`process.env.NODE_ENV = 'production'`をしないとかなり不利な結果を得る。

> I tend to agree that "compiling server side code with webpack to remove access to process" is not what Node developers would typically do, and likely many React users are not even aware of this possibility. So the current results are probably more representative of React SSR perf in the wild. However, it can also be a bit unfair to not show React's perf with full optimization.

- [Compiled out process.env.NODE_ENV for Rax and React benchmarks by aickin · Pull Request #10 · raxjs/server-side-rendering-comparison](https://github.com/raxjs/server-side-rendering-comparison/pull/10#issuecomment-273012995 "Compiled out process.env.NODE_ENV for Rax and React benchmarks by aickin · Pull Request #10 · raxjs/server-side-rendering-comparison")

サーバサイドも`process.env.NODE_ENV === 'production'`でuglifyしてdead codeを消し去ったReactを使うことでデバッグ情報がないものとなる。
Reactが公式で配っているbundle済みのもの(min.js)を使うのが手早い。

- webpackのaliasでproduction build時に差し替える
  - <https://github.com/facebook/react/issues/8788#issuecomment-277440046>
- [Server rendering is slower with npm react · Issue #812 · facebook/react](https://github.com/facebook/react/issues/812 "Server rendering is slower with npm react · Issue #812 · facebook/react")
- [Hoist `__DEV__`'s process.env use out of hot code. by STRML · Pull Request #86 · facebook/fbjs](https://github.com/facebook/fbjs/pull/86#issuecomment-285204734 "Hoist __DEV__&#39;s process.env use out of hot code. by STRML · Pull Request #86 · facebook/fbjs")

将来的には、Reactはdevとprodのbundleをそれぞれ配布し`REACT_ENV`でどちらを使うか設定出来るようになるかも とのこと。
(`NODE_ENV`も引き続き見る感じ)

- [RFC: Throw in CommonJS entry points if NODE_ENV is not "development", "test", or "production" · Issue #9245 · facebook/react](https://github.com/facebook/react/issues/9245 "RFC: Throw in CommonJS entry points if NODE_ENV is not &#34;development&#34;, &#34;test&#34;, or &#34;production&#34; · Issue #9245 · facebook/react")

## パフォーマンス計測

- サーバを立てて `ab -n 100 -c 10` とかで計測
- 参考:
  - [React with Reduxによる大規模商用サービスの開発 / nodefest2016 // Speaker Deck](https://speakerdeck.com/yoshidan/nodefest2016 "React with Reduxによる大規模商用サービスの開発 / nodefest2016 // Speaker Deck")
  - [アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ](https://developers.cyberagent.co.jp/blog/archives/3513/ "アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ")
- ボトルネック
  - データ取得周り
     - データ取得してrenderが完了するまでレスポンスが返せない
  - サーバ CPU/メモリ
     - renderToStringがCPU boundな処理
  - クライアント CPU/メモリ

## デバッグ

`node --inspect server.js` で Chromeでプロファイルを取ってみる

- CPU Profileを取る
- "Chart"表示にすれば見慣れたタイムラインでプロファイルを読める

![debug](https://monosnap.com/file/spzfcFA5nOWVnsW4sdWedpPmo9R7cr.png)

## 最適化の種類

- Cache
  - LRUなキャッシュがよく使われる
  - ユーザー属性はキャッシュしない工夫が必要
- Partial Rendering(Above The Fold Render)
  - 見えている部分のみをサーバでレンダリングして返す
  - [React with Reduxによる大規模商用サービスの開発 / nodefest2016 // Speaker Deck](https://speakerdeck.com/yoshidan/nodefest2016 "React with Reduxによる大規模商用サービスの開発 / nodefest2016 // Speaker Deck")
  - [React + Reduxで作ったIsomorphic(Universal) JSなサービス開発の裏側 - Tech Blog - Recruit Lifestyle Engineer](http://engineer.recruit-lifestyle.co.jp/techblog/2016-11-16-isomorphic-javascript/ "React + Reduxで作ったIsomorphic(Universal) JSなサービス開発の裏側 - Tech Blog - Recruit Lifestyle Engineer")
  - [Using Electrode to Improve React Server Side Render Performance By Up To 70%](https://medium.com/walmartlabs/using-electrode-to-improve-react-server-side-render-performance-by-up-to-70-e43f9494eb8b "Using Electrode to Improve React Server Side Render Performance By Up To 70%") 
- Renderの変更
  - `renderToString`が同期で重いのがボトルネック
  - render自体を非同期で行う試み
  - [gabrielbull/react-router-server: Server Side Rendering library for React Router v4.](https://github.com/gabrielbull/react-router-server "gabrielbull/react-router-server: Server Side Rendering library for React Router v4.")
    - `renderToString`を非同期(AsyncRendererというコンポーネントでラップしてる)

> ちなみに一番最初にmemcachedではなく、Redisを使いました。しかし、Redisにおいてオブジェクトのサイズが10kbを超えて、特にevictionが発生するとパフォーマンスが急激に落ちます。そのため、途中memcachedに切り替えました。

- [アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ](https://developers.cyberagent.co.jp/blog/archives/3513/ "アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ")

現実的な解として

- renderToStringが遅い問題をどうにかする必要がある
  - レンダリングするコンポーネントを減らす
  - 結果をキャッシュして返す
  - の2つが主と解となり両方を組み合わせる事が多い
- キャッシュ
  - ユーザ情報を含んだrender結果をキャッシュすると問題がおきやすい
  - ユーザ情報だけサーバではrenderしないようにするコンポーネントの実装が必要になる

TODO:

- [ ] `renderToString`を[Cluster](https://nodejs.org/api/cluster.html "Cluster")で並列化するような事例もある?
- [ ] キャッシュするにはユーザ情報をコンポーネントから隠すスイッチがいる
  - これをミスなく上手くやる方法ってあるのかな? うっかり事故りそう
  - [ReactJS SSR Profiling and Caching – WalmartLabs – Medium](https://medium.com/walmartlabs/reactjs-ssr-profiling-and-caching-5d8e9e49240c "ReactJS SSR Profiling and Caching – WalmartLabs – Medium")
  - Component Cache-ability

## 目安

レスポンスまで100ms以下

- cache: 30ms
  - [React server side rendering performance](https://www.slideshare.net/nickdreckshage/react-meetup "React server side rendering performance")

> キャッシュされたページのレスポンスタイムは50ms以下になっています。

[アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ](https://developers.cyberagent.co.jp/blog/archives/3513/ "アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ")

## 事例

- [Reactサーバーサイドレンダリングに関するリソースまとめ - Qiita](http://qiita.com/okmttdhr/items/edb42608d51998eaf37a "Reactサーバーサイドレンダリングに関するリソースまとめ - Qiita")
- [アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ](https://developers.cyberagent.co.jp/blog/archives/3513/ "アメブロ2016 ~ Isomorphic JavaScriptの詳しい話 | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ")
- [アメブロ2016 ~ React/ReduxでつくるIsomorphic web app ~ | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ](https://developers.cyberagent.co.jp/blog/archives/636/ "アメブロ2016 ~ React/ReduxでつくるIsomorphic web app ~ | CyberAgent Developers Blog | サイバーエージェント デベロッパーズブログ")
- [Using Electrode to Improve React Server Side Render Performance By Up To 70%](https://medium.com/walmartlabs/using-electrode-to-improve-react-server-side-render-performance-by-up-to-70-e43f9494eb8b "Using Electrode to Improve React Server Side Render Performance By Up To 70%")
- [React with Reduxによる大規模商用サービスの開発 / nodefest2016 // Speaker Deck](https://speakerdeck.com/yoshidan/nodefest2016 "React with Reduxによる大規模商用サービスの開発 / nodefest2016 // Speaker Deck")
  - [リクルートテクノロジーズのフロントエンド開発 2016 - from scratch](http://yosuke-furukawa.hatenablog.com/entry/2016/12/01/175446 "リクルートテクノロジーズのフロントエンド開発 2016 - from scratch")
  - redux-async-loaderでのデータ取得
  - React Router v4に移行はできてない
  - [WIP: chore:[semver-major] upgrade react-router v4 by yosuke-furukawa · Pull Request #5 · recruit-tech/redux-async-loader](https://github.com/recruit-tech/redux-async-loader/pull/5 "WIP: chore:[semver-major] upgrade react-router v4 by yosuke-furukawa · Pull Request #5 · recruit-tech/redux-async-loader")
- [LWJGL/lwjgl3-www: The LWJGL 3 website.](https://github.com/LWJGL/lwjgl3-www "LWJGL/lwjgl3-www: The LWJGL 3 website.")
   - [Server-side rendering, code-splitting, and hot reloading with React Router v4](https://medium.com/@apostolos/server-side-rendering-code-splitting-and-hot-reloading-with-react-router-v4-87239cfc172c "Server-side rendering, code-splitting, and hot reloading with React Router v4")

## 未来

- [Add package builds for new server renderer and enable tests by sebmarkbage · Pull Request #9710 · facebook/react](https://github.com/facebook/react/pull/9710 "Add package builds for new server renderer and enable tests by sebmarkbage · Pull Request #9710 · facebook/react")
  - ReactでStreamRenderが実装中
  - renderToStringの負荷がもう少し分散するはず
  - いわゆる[react-dom-stream](https://github.com/aickin/react-dom-stream "react-dom-stream")の公式実装