この記事はAkatsuki Advent Calendar 2016の7日目の記事です。
こんにちは、 @shimpeiws です。
アカツキのライブエクスペリエンス事業部で、Webエンジニアをしています。
今日は最近開発しているプロダクト、Wowful(現在β公開中)のアーキテクチャについて書きます。
※ 今回の記事は以前発表した 2つのスライドの増補改訂版のポジションです。
Wowfulのサービス面の特徴と技術的な要求
Wowfulはサービスとして以下のような特徴を持っており、
これらの要求を解決できる技術・アーキテクチャの選定を行う必要がありました。
- まずはWeb主導で展開する
- BtoCのサービス
- 利用ユーザはスマートフォンが多数になる予想
- 検索からの流入を主な導線として想定
- トラフィックが多いページのユーザインタラクションが複雑
- SEOが大事
Wowfulのアーキテクチャ
Wowfulは以下の図のようなアーキテクチャ上で動作しています。
ServerSideRenderingするときのフロー
SPAとして動作する時のフロー
特徴的なのは、**"Isomorphic JavascriptでSSR(サーバサイドレンダリング)を実現している"**という点なのでこの点について掘り下げて紹介していきます。
なぜSSR(サーバサイドレンダリング)したかったか?
理由は2つで、1つ目はSEOのため、2つ目はユーザビリティ向上のためです。
SEO対策としてのSSR
"Wowfulのサービス面の特徴と技術的な要求"の項で触れましたが、
サービスの特徴として検索からの流入に重きをおいており、そのためSEOが非常に重要な位置を占めています。
SSRを導入する大きな動機として、SEO対策がありましたが、
GoogleのクローラはJSでの描画まで含めて認識している、という話もあり、効果的な選択であったかは疑問が残ります。
リクルートテクノロジーズ・リクルートライフスタイルの吉田尚弘さんのスライド、React with Reduxによる大規模商用サービスの開発 では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を使えば簡単にReactでのSSRを実現することができますが、
SSRしつつSPAでも動作させることを考慮した場合、
初期表示用のデータ取得ロジックをRails側にもJavaScript側にも持つことになり、
ロジックが別の言語で重複してしまうことになります。
しかし、必ずしもIsomorphic Javascriptの採用だけがReactでSSRする際のベストプラクティスではありません。
r7akamuraさんのRuby on Rails on React on SSR on SPAは、react_on_rails
のgemを中心に、つなぎこみのコードを丁寧に作ることで、Railsに寄せたSSRの構成を実現しています。
boilerplate
Isomorphicな構成のガイドラインとして、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が先のboilerplateでは採用されていますが、
Wowfulではリクルートテクノロジーズ社がOSSとして公開しているrecruit-tech/redux-async-loaderを採用しました。
非常にシンプルなインタフェースながら、必要十分な機能を備えており、
例えば以下のようなコードでSSR時のデータの待受が可能です。
@asyncLoader((props, store) => (
store.dispatch(fetchItem(props.params.id))
))
まとめ
実際にプロダクションで運用しているアーキテクチャやその選定理由について紹介しました。
- SEOとユーザビリティ向上のためにSSRすることにした
- Isomorphic Javascriptのメリットを享受するためにNode.jsのサーバを用意した
- Isomorphicな構成の参考としてboilerplateを参照した
- データ取得完了の待受のために
redux-async-loader
を使っている
同じような構成を考えている方の参考になれば幸いです。
さて、明日の担当はウォーリーの仮装をさせると並ぶものなし、@paranishian です!