App Shell モデルの素振り(後編)SSR なき世界観を模索してみた

3. その他 SPA にありがちな懸念点の検討

書き終わって読み直すに、いくつかの方法論は浮かぶが思った以上にやってみねぇと分からんという感じです。

ここでは SPA + SSR における完全な HTML を生成するサーバーサイドと、SPA + BFF (Backend For Frontend) くらいのゆるやかなサーバーサイドを別のアプローチとします。どのみち完全に静的な HTML と API サーバだけで複雑な SPA を成り立たせるのは無理があるので BFF の支援は前提に入ります。

また、SPA + SSR のダルさの根源は node の CPU ぶん回し対策や、キャッシュサーバーの管理などを伴うキャッシュストラテジーの煩雑性と定義します。単純に SPA + SSR を実現するだけなら道具も成熟してきているので、さほど難しくはなくなってきているという扱いにしておきます。

3-1. 初回表示の速度

初回に限らず RAIL モデルでいえばページロード速度の目標値は、人間の認知の特性に関する実験結果を背景にざっくり 1,000ms 以内とされています。全リソースのフルロードを1秒で済ませるのは困難ですが、主要コンテンツの表示までが1秒以内なら上等です。速ければ速いほど良いとも言えますが、目標値を超えて過剰な複雑性を抱えるのは避けたいところ。

SPA は巨大なバンドルになりがちな JavaScript のダウンロードを待つ必要があるので、キャッシュのない初回表示のページロード速度で全体的に大きなハンデを抱えています。アプリケーションコードの分割や、依存ライブラリの節制で軽減はされますが、ハンデをゼロにはできません。

これは Polymer + PRPL 周りのアプローチでも HTML Imports 待ちがあるので同様になると思われる。プッシュプッシュ。

SPA + SSR の強みにどれだけ迫れるか

手塩がかけられた十全な SPA + SSR に正面から勝つのは難しいポイントです。SPA + SSR の強みは、巨大化しがちな JavaScript のダウンロードを待たなくても、完成された HTML を返すことで CSS さえあれば即時コンテンツを表示できる点であり、HTML のレスポンスさえ速ければ強力です。

2回目以降の表示については Service Worker によるキャッシュが有効になれば、First Meaningful Paint までの時間も良い勝負ができそうです。キャッシュが有効なら必要なのは API リクエストの往復 + クライアントサイドの HTML 構築ほかレンダリング時間だけになります。

SSR までやらない BFF で初回表示をよくできそうなこと

他、初回表示について SPA + BFF で完全な HTML を生成するまではしないけど…くらいでやれることは...

  • App Shell HTML でないリクエストは初期表示用の JSON データを埋め込むとか?
    • キャッシュの複雑性は SSR よりは低いが、URL と必要データの関連付けは BFF 内に残る
    • とはいえ OGP 生成とかの責務を考えると、その程度はどのみち持つことになるのかも
  • App Shell HTML でないリクエストは各種ファイルを Server Push するとか?(微妙)
    • クライアントキャッシュを判定できないと非 App Shell 環境には毎回送りつける→UA判定すればいいか
    • みんなが使うライブラリは CDN とか共有できるキャッシュを今こそ活かしたい感があるかも
  • AMP 越しに限っては amp-install-serviceworker を頼りたい?
    • が…AMP HTML 作ってる時点でほぼ SSR じゃん!→ 良い感じに AMP 生成するサービス….
    • AMP HTML に CPU ブン回さない古風なテンプレート使っても良いけど二重管理ダルそう

あたりでしょうか。

工夫次第で良い感じにする手段はありそうなので、あとは実践の機会があれば実測ベースで必要十分なパフォーマンスを出せるか検討する感じになりそうです。

3-2. Googlebot 対策 ( SEO )

日本国内の開発者が普段相手にしている Googlebot は、JavaScript を実行したあとのレンダリング結果を"基本的には"取得できます。

基本的には、という但し書きは色々な意味で”難儀"な JavaScript を含んでいると実行できなかったり、色々な都合で凝ったアクセス制御があるときにうっかり Googlebot (の動いているサーバ)を拒絶してしまったり、とかの事故が起こる余地があるということです。

これが 【Google SEO】JSフレームワークを使ったサイトではプリレンダリングを推奨 | 海外SEO情報ブログ でも述べられてるような SEO 視点のメリットです。じゃあ実際に fetch as google がそんな不安定か?というと特殊事例を除いて全体的には素直に動いているような肌感があります。

prerender.io 的または事故防止のソリューション

prerender/prerender を久々に手元でまわしてみましたが相変わらず PhantomJS を利用しているみたいで、fetch as google のレンダリングよりも厳しいです。いざ本格的に不明なトラブルが起こったときの問題解決は fetch as google のほうが大変ですが。

最新の Headless Chrome が使えれば、無理のないレンダリング結果を得られると期待されるので、prerender.io の進化か他の同様なソリューションの台頭を待ちたいところです。

fetch as google と同等の環境を手元で実行できれば、レンダリング結果に関するデータに閾値を指定して自働テストさせるなどで事故防止を実現するアプローチもありそうです。prerender.io のようなサービスを使っても同様の事故は起こりうるので、何らかの気遣いは求められるでしょう。

3-3. OGP などへの対策 ( SNS )

先に述べた prerender.io の類がまだ厳しそうな雰囲気を前提にすると、App Shell モデルを採用していても OGP 周りに限っては SSR が必須です。通常の HTML リクエスト時と App Shell 時で出しわけが必要になります。ロジックの共通化や API アクセスの抽象化は必要ですが、SPA + SSR の根源的な複雑性と比べればさほど大きい問題ではありません。

ネイティブアプリも使ってそうな API サーバー以前のレイヤーで普段通りスケールする限りであれば、クライアントサイドの中間生成結果を巧みにキャッシュするようなSSR のダルさは顕在化しません。まあ、この場合でも LRU キャッシュくらいは持ってよいかもしれませんが。

通常の HTML リクエスト時

Service Worker が有効になる前(または Service Worker が無効な環境)では次のように入るべき情報を入れて SSR で返します。ejs なり pug なり hbs なり好きなテンプレートエンジンで良いでしょう。

<!-- 普通にサーバーで HTML を生成する -->
<title>タイトル</title>
<meta name="description" content="ディスクリプション">
<meta name="twitter:title" content="タイトル">
<meta name="twitter:description" content="ディスクリプション">
<meta property="og:title" content="タイトル">
<meta property="og:description" content=:ディスクリプション">

App Shell リクエスト時

Service Worker が有効な場合に使用される App Shell の HTML は次のような状態ですが、コンテンツのレンダリング時に <title> を書き換えます。他の <meta> は人間の確認用に処理してもよいですし、BOT は喰わないのでそもそも省略しても構いません。

<title><!-- ※動的に挿入する --></title>

<!-- ※以下は必要に応じて -->
<meta name="description" content="">
<meta name="twitter:title" content="">
<meta name="twitter:description" content="">
<meta property="og:title" content="">
<meta property="og:description" content="">

以上、模索でした

SPA の懸念点を潰す場合に現状でも良いセンまでいけそうですが、これはこれでそれなりの努力やノウハウの構築が求められるな、という予想通りの雰囲気でした。今回スルーした観点としては次のようなポイントもあります。

  • オフライン時のユーザー体験をいじくりまわせる(App Shell SSR もやりようあるけど
  • クライアントサイドで起こりうる事故に比較的強い(SSR)

どのアプローチがマシかは判断がいかようにも分かれます。SSR ダルいとは言っていますが、SPA + SSR であってもログインなどユーザーによってコンテンツが異なる状態がなければキャッシュの持ち方もだいぶ雑にできるので許容できるケースも増えるはずです。また、人的リソース含めて色々なものが潤沢なら、SPA + SSR + App Shell で死角をなくすという選択肢すらありえます。

サービス特性やプロダクト要件、チームビルディングなど環境条件を言い始めるとキリがありませんが、社内の一部で弁証法が流行っているので、引き続き次の止揚を探って参りたいと思います。( ╹◡╹)ノシ

はい。当たり前のこと言わせんなよ恥ずかしい (*ノノ