ReactでSSR(サーバサイドレンダリング)している話

この記事は[Akatsuki Advent Calendar 2016](http://qiita.com/advent-calendar/2016/aktsk)の7日目の記事です。

こんにちは、 @shimpeiws です。
アカツキのライブエクスペリエンス事業部で、Webエンジニアをしています。

今日は最近開発しているプロダクト、[Wowful](https://wowful.com)(現在β公開中)のアーキテクチャについて書きます。

※ 今回の記事は以前発表した :point_down: 2つのスライドの増補改訂版のポジションです。

- [
サーバサイドレンダリング、してますか?](https://speakerdeck.com/shimpeiws/sabasaidorendaringu-sitemasuka)
- [
BFFにかける期待](https://speakerdeck.com/shimpeiws/bffnikakeruqi-dai)

## Wowfulのサービス面の特徴と技術的な要求

Wowfulはサービスとして以下のような特徴を持っており、
これらの要求を解決できる技術・アーキテクチャの選定を行う必要がありました。

- まずは**Web主導**で展開する
- BtoCのサービス
- 利用ユーザはスマートフォンが多数になる予想
- **検索からの流入**を主な導線として想定
- トラフィックが多いページの**ユーザインタラクションが複雑**
- **SEO**が大事

## Wowfulのアーキテクチャ

Wowfulは以下の図のようなアーキテクチャ上で動作しています。

ServerSideRenderingするときのフロー

![BFFにかける期待.006.png](https://qiita-image-store.s3.amazonaws.com/0/29637/c1ded368-261f-9562-1c27-6cbaa9da35a0.png)

SPAとして動作する時のフロー

![BFFにかける期待.007.png](https://qiita-image-store.s3.amazonaws.com/0/29637/a173a56e-7029-73ad-7160-c68b3de7e41d.png)

特徴的なのは、**"Isomorphic JavascriptでSSR(サーバサイドレンダリング)を実現している"**という点なのでこの点について掘り下げて紹介していきます。

## なぜSSR(サーバサイドレンダリング)したかったか?

理由は2つで、1つ目は**SEO**のため、2つ目は**ユーザビリティ向上**のためです。

### SEO対策としてのSSR

"Wowfulのサービス面の特徴と技術的な要求"の項で触れましたが、
サービスの特徴として検索からの流入に重きをおいており、そのため**SEO**が非常に重要な位置を占めています。

SSRを導入する大きな動機として、SEO対策がありましたが、
**GoogleのクローラはJSでの描画まで含めて認識している、という話もあり**、効果的な選択であったかは疑問が残ります。

リクルートテクノロジーズ・リクルートライフスタイルの吉田尚弘さんのスライド、[React with Reduxによる大規模商用サービスの開発](https://speakerdeck.com/player/50bee44576e14c83a3489db877ea863e#) ではFetch as GoogleとPage Speed Insightの結果も提示されており、
GoogleのクローラはSPAであっても全コンテンツを認識しているという記載もあります。

### ユーザビリティ向上のためのSSR

もう一つのSSRの目的として**ユーザビリティ向上**があげられます。

外部からランディングしたユーザを、真っ白なページやローディングで待たせること無く、
コンテンツが描画された状態で迎える事ができるのがメリットです。

実際的にはブラウザの標準的な動作にあわせている、という側面が強いので、ユーザビリティ向上というよりは、
SPAの動作をブラウザに合わせる努力をする、という方が正確かもしれません。

## Node.js、 Isomorphic Javascript

Wowfulのアーキテクチャでは、SSRするために、**Node.js**のサーバ(図中のPresentation Server)を置いています。
この構成を採用したのは、SSR時とクライアントでのSPAでの動作時で同じソースコードを使える = **Isomorphicに動作させることができる**ことにアーキテクチャ上のメリットを感じたためです。

例えば[react-rails](https://github.com/reactjs/react-rails)を使えば簡単にReactでのSSRを実現することができますが、
SSRしつつSPAでも動作させることを考慮した場合、
初期表示用のデータ取得ロジックをRails側にもJavaScript側にも持つことになり、
**ロジックが別の言語で重複してしまう**ことになります。

しかし、必ずしもIsomorphic Javascriptの採用だけがReactでSSRする際のベストプラクティスではありません。

r7akamuraさんの[Ruby on Rails on React on SSR on SPA](http://r7kamura.hatenablog.com/entry/2016/10/10/173610)は、`react_on_rails`のgemを中心に、つなぎこみのコードを丁寧に作ることで、Railsに寄せたSSRの構成を実現しています。

### boilerplate

Isomorphicな構成のガイドラインとして、[erikras/react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example) を参照しました。
ただし、作成から時間がたっており、npmのパッケージも古くなっているものが多いので、
構成の参考に留めておくことをおすすめします。

### redux-async-loader

ReactでSSRする時に1つ難関となるのは、**データ取得完了の待受**です。

通常のReactアプリケーションであれば、初期表示用のデータが取得できるまでloadingを表示しておき、
データが揃った時点でstateの更新 -> Viewの更新といった流れが連続的に起こります。
しかしSSRの場合には、HTMLとして返却するのは一度なので、
データ取得完了 -> React.renderToString -> HTML返却のタイミングを決める必要があり、
データ取得完了を待ち受ける必要が出てきます。

そのためのnpmモジュールとして、[Rezonans/redux-async-connect](https://github.com/Rezonans/redux-async-connect)が先のboilerplateでは採用されていますが、
Wowfulではリクルートテクノロジーズ社がOSSとして公開している[recruit-tech/redux-async-loader](https://github.com/recruit-tech/redux-async-loader)を採用しました。

非常にシンプルなインタフェースながら、必要十分な機能を備えており、
例えば以下のようなコードでSSR時のデータの待受が可能です。

```js
@asyncLoader((props, store) => (
  store.dispatch(fetchItem(props.params.id))
))
```

## まとめ

実際にプロダクションで運用しているアーキテクチャやその選定理由について紹介しました。

- **SEO**と**ユーザビリティ向上**のためにSSRすることにした
- **Isomorphic Javascript**のメリットを享受するためにNode.jsのサーバを用意した
- Isomorphicな構成の参考としてboilerplateを参照した
- データ取得完了の待受のために `redux-async-loader` を使っている

同じような構成を考えている方の参考になれば幸いです。

---

さて、明日の担当はウォーリーの仮装をさせると並ぶものなし、@paranishian です!