React+jQuery+RailsのSPAをサーバサイドレンダリングに移行した件(その1:概要編)
先日、「チェンクロパーティーシミュレーター」(以下「ccps」)をアップデートしまして、
サーバサイドレンダリング(いわゆる「SSR」)に対応しましたヽ(`・ω・´)ノ
過去の経緯はこちらを見ていただきたいのですが・・・
・・・すでに「React+jQuery」で動いており、「TypeScript」で書かれたSPAでございます
しかし、SPAであるがゆえに、大きな問題を抱えておりまして、
それは「とにかく初期表示が遅い」ということです(´-ω-)
これを解決するのが「サーバサイドレンダリング(SSR)」なのですが、
いろいろ検索してみると、わりと否定的な意見が多くあります
やってみた上で、私もどちらかといえば否定的なのですが、
要は「どこで使うべきものなのか?」が重要だと思うのですね
SSR自体、わりと手間がかかるし、それ用の設計や手法が必要になるものであり、
私もかなり苦労させられたのですが、そこを紐解いていくためにも、
今回はあえて「当たり前のこと」から考えてみます...φ(・ω・`)
なお、かなり長くなる予定なので、概要編・実戦編と分けた上で、
全部で数回になる予定です
<第2回>
parrot.hatenadiary.jp
そもそもサーバサイドレンダリング(SSR)とは何か?
冷静に考えてもらいたいのですが、
そもそも「サーバサイドでHTMLを出力し、クライアントのJavaScriptで制御する」というのは、
Webシステムとしてはごく「普通」の話です
にもかかわらず、わざわざ「SSR」なんて仰々しい言葉を定義するわけですから、
「サーバサイドでHTMLを作ること」だけではないわけです
そもそも、現代的なWebシステムにおいて、サーバサイドがAPI的なものを提供し、
クライアントが(旧来に比べるとややリッチな)jsで制御する・・・というレベルの設計は、
すでに一般的といっていいレベルだと(少なくとも私は)思ってます
その「リッチなjs」が単なる「制御」を超えて、「画面を大幅に書き換える」、
つまりは「view」を直にいじるレベルになってくると、
クライアントのview制御と、サーバサイドのview生成で、ロジックが「二重化」されることになります
これを解消するため、「viewは全部クライアントに寄せる」ことをした上で、
「画面の切り替えのレベルまで全部viewの制御に寄せる」、
「サーバサイドは純粋なAPI」という設計方針をとったのが「SPA」ということですね
しかし、「クライアントでviewを作る」ということは、
「クライアントでjsを解釈しない限り、画面が見えない」という問題があります
(いわゆる「ファーストビューが遅い」問題)
サーバでHTMLを作っていれば、少なくともその範囲ではすぐ表示されるのですが、
SPAの場合はjsの通信速度やクライアントのCPU速度など、
様々な要因が絡むので、相対的には「遅い」わけです(´-ω-)
一般的に、SPAの問題としてあげられるのはこのあたりでしょうか
私のccptsの場合は「ツール」なので、編集モードで多少待たされるのはともかく、
共有されたURLから飛んでくる「PT閲覧モード」で、
編集用のコードを解釈する時間で待たされる・・・というのは、本来よろしくありません
同様に、例えばBlogやニュース記事のような、
「ユーザーがURLから飛んできて、ただ見たいだけのサイト」は、
基本的にSPAに向いてません(´・ω・`)
そういったサイトは、普通にサーバサイドでHTMLを出力すればいいわけで、
そもそも「SSR」を検討する以前の問題です
言いかえれば、まずSPAにすべきかの検討があって、
SPAが必要だけど、それでは遅いという場合に、
初めてSSRが検討される・・・というのが本筋かと
じゃあ、SPAにできればSSRは簡単なのかというと、
そんなことはないわけで、めっちゃコストがかかります
Webで良く見る「SSR不要論」は、
「理想的ではあるけど、移行するコストに見合わない」ということだと思ってます
SSRの問題点はこんな感じかと
(あとで出てきますが、この「SSRの問題点」は、
「SSRに移行するための段取り」と関連してきます)
- (SPAとして完成していたとしても)実装コストがかかる
- 「ブラウザ」で動作することを前提に、暗黙的に書かれた仕様を全て見直す必要がある
- 例:windowオブジェクトへの参照・暗黙的なイベントへの依存
- 「ブラウザ」で動作することを前提に、暗黙的に書かれた仕様を全て見直す必要がある
- クライアントとサーバサイドの担当範囲があいまいになる
- クライアントだけが知っていればよかったことを、サーバサイドでも考慮しないといけない
- (通常のHTML出力に比べて)処理が重い
これらのメリットとデメリットをふまえた上で、
今作ろうとしているシステムに対し、「普通のHTML出力+js」がいいのか、
「SPAのSSRがいいのか」を判断が必要になってきます
<参考>
STEP0:SPAをSSRにするための概要設計
(1) SSRを阻害する要因
今回のSSR移行に関しては、以前から使っていた「React on Rails」の機能を利用しています
以前は「SPAをRailsに絡めて簡単にデプロイできるツール」的に使っていたのですが、
元々SSRの機能がついてまして、SSRを試すだけならprerenderフラグをtrueにするだけです *1
<%= react_component("HelloWorld", props: @some_props, prerender: true) %>
もちろん、「それだけ」でSSRが動いてくれるなら、
React on Railsを開発している会社のコンサル業は成り立たないわけで、
そんな単純な話ではありません(´・ω・`)
「サーバサイドでjsを動かす」というのは、
Railsの文脈においては「execjs」のことを指します*2
(一般的にはNode.jsのサーバサイドプロセス・・・ですかね)
execjsはサーバサイドでのjs実行系なので、「ブラウザ」の概念がありません
SSRを試して、おそらく最初に目にするエラーは、
「windowオブジェクトがない」というものでしょう
windowと書いてなくても、例えば「location」や「confirm」のように、
暗黙的にwindowオブジェクトを参照しているものはたくさんあります
そういったものは全て、サーバサイドjsでは動作しないことになります
React世代のライブラリはともかく、
旧世代のライブラリはほぼ確実にwindowを触っているはずで、
その代表格はもちろんjQueryということになります
私のccptsにおいて、jQueryは仕様上必須です
しかし、jQueryに依存している限り、実行系がエラーを吐いてしまいます
ここが、今回の設計における最大の肝になります
(2) SSRに使うjsの制約と、その対処
ところで、「React on Railsが実行しているjsファイル」ってどれなんでしょう?
configを見ると、こんな設定項目があります
# config/initializers/react_on_rails.rb から抜粋 ReactOnRails.configure do |config| # buildに使うコマンド config.build_production_command = 'RAILS_ENV=production bin/webpack' # SSRで実行するjs名 config.server_bundle_js_file = 'ccpts.js' end
ここで指定している「ccpts.js」がどこにあるかというと、
エラーメッセージから追った結果、「public/packs/ccpts.js」にありますが、
これは「app/javascript」以下のjsをwebpackで固めたファイルになります
React on Railsに限らず、SSRしようとした場合、
webpack等で固めた単一のjsを、クライアントとサーバで共有し、
サーバサイドでも実行する・・・という流れになります
そして・・・ここがポイントなのですが・・・
裏を返せば、「サーバサイドで実行するjsにwindowオブジェクト等は含まれず、
クライアントで実行する際には含まれる」という状況が作れれば解決するはずなのです
実際、これを試したことでSSRに成功したのですが、
概要を図にまとめるとこうなります
解説については次回以降やっていきますが、
私が立てたSSR化の段取りは以下のようになります
(まさに、先ほど挙げた「SSRの問題点」と対応しております)
- ブラウザ系オブジェクトの排除(とりあえず実行時エラーを消す)
- 「仕様上の正しい動作」になるように修正
- インフラの調整
次回は「実践編」として、先ほどの図の解説をしながら、
どのように先ほどの問題を解決したのか・・・を書いていきます...φ(・ω・`)
<続き>