🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

なんでもSPAにするんじゃねぇ!という主張のその先

この主張、界隈(少なくとも自分の観測範囲)では割とよく見かけるし、なんか定期的に話題になるトピックなのかなーと。

まあ持論としてもコレには概ね同意しており、会社のスタンスとも相まって、常日頃からぼんやり考えてたりすることでもある。

で、そんな折にこのツイートを発見して、さらにそれに言及してる人々を見て、ふと自分でも現状を整理しておきたいなーという気持ちになったので筆を執った次第。

つまりそういうポエムです。

元ツイの内容ざっくり

  • ショッピングサイトをSPAにするんじゃない!
    • JSというチェーンソーで彫刻を作ろうとしてるようなもん
  • SPAにできるからといって、SPAにしていいってもんではないのだ
    • 自家用車にロケットブースター載せるか?
  • これはロースペックな環境やECサイトをずっとやってきての経験
  • イマドキのWeb開発っていうと、ReactでReduxでGraphQLで・・ってなりがち
  • JSをオフにしてぜんぶSSRしろ
    • a11yとかはひと手間いるかもしらんけども
  • それでも動的な部分は必要ではって?
    • わかった、じゃあチームの中の限られたメンバーでだけ使え
    • パフォーマンスを保証する前提でなら、JSを使ってもよい
  • なんしかJSは限られたところでだけ使え
    • フルスタック?フルストップの間違いでは?
  • いいからJSは用法用量を守って使え!

という感じに激おこ・・!けど、まあその気持ちもわかるわ〜って感じ。

というところを、Solid.jsの中の人であり、Markoのコアメンバーでもある`@RyanCarniato`氏が言及してたのを発見。

Ryan氏の言及スレざっくり

  • そういうケースはMPAでやるのがよい
  • そして今のところ、Markoがベストな選択肢だと思ってる
    • もちろん誇張してるところはあるけど
    • Twitterってそういうところやし
  • たとえば競合を並べてみる
    • AstroとかElder.jsがあるのは知ってる
    • Qwikも最近出てきたところ
  • いわゆるSPA出自なツールは軸足が違う認識なので外す
    • Next, Nuxt, SvelteKit
  • Astroはその仕組みの中でこれらのUIライブラリを使えるのがよい
    • けどまだ始まったばかりのプロジェクト
  • この気持ちは2012-2013年くらいに感じたやつに近い
    • Reactが出てきて、SPAすごい!みたいな時期
  • あの頃と違うのは、青写真が既にあること
    • Markoはこの分野をもう7年近くもやってる
  • 近いうちにくるトレンドとして予想してるのは、以下をすべて満たす仕組み
    • 1. コンパイラドリブンで部分的hydrationできるもの
    • 2. 順不同・オンデマンドで遅延hydrationできるもの
    • 3. 任意に停止・再開できるhydrationができるもの
    • 4. ストリーミングでSSRできるもの
  • これを全部まとめてやるのは厳しいのでは?(という質問に対して)
    • 現状、それぞれできてるもの、できてないものが別れてる
    • 1はMarkoで(AstroもElder.jsもできるような?)
    • 2はAstroとQwikで
    • 3はMarkoとQwikで
    • 4はMarko

とのこと。

同じMPA推しの人がいることがわかってよかった。

補足: MPA(MultiPageApplication)

ここでいうMPAは、複数のHTMLファイルからなるページで、1つのアプリ・サイトを構成するやり方のこと。各ページ間は単なるハイパーリンクで、互いに依存しない。

SPAはその反対に、1つのHTMLファイルが全ページをまかない1つのアプリを構成するやり方。

MPAな構成は個人的にも推してて、去年のこの記事も大まかに同じ趣旨だった。

静的サイトジェネレータ、もといMPA向けbetter webpackとしてのNext.js - console.lealog();

補足: Marko

GitHub - marko-js/marko: A declarative, HTML-based language that makes building web apps fun

国内ではあんまり話題になってないイメージではあるけど、あのeBay社がやってるOSSフレームワーク

  • Svelte同様、コンパイルして使う
  • クライアントにはJSが必要な部分のコードだけ小さく返せる
  • HTMLライクな独自のシンタックスを`.marko`ファイルに書く
  • SSRした結果はストリーミングして返すこともできる
  • 基本的には`node build.js`的な感じでサーバーを建てて使うデザイン

コンパイラがものすごくチューニングされてて、コンポーネントは`import`せずにどこでも使えたり、事細かく`webpack`したチャンクを吐けるとのこと。

特に、SSRした結果を真にストリーミング(HTTPの`Transfer-Encoding: chunked`)で返せるのは、現状だとMarkoだけらしい。

補足: Astro, Elder.js, Qwik

Markoと同じグループとされていたフレームワークたち。
比較対象のグループには、NextとかNuxtとかSvelteKitとか、いわゆる有名どころが並ぶ。

これらはどれも独自のコンセプトがあって、人類の発想力はすごいな・・ってなるので要チェック。

補足: ○○ Hydration

Hydrationとは、事前レンダリングされた状態(乾いてる)に、お湯をかけて戻すイメージで、クライアントサイドでJSのイベントリスナを設定したり状態を復元したりと、JSを実行すること。

Nextとかの場合、その後のルーティングのためにその分のコードが落ちてきてて、このタイミングで実行される。SvelteKitは設定でこれを完全に落とすこともできるけど、これらは基本的にはページ単位でしか制御できない。

つまり、ページの大部分は事前レンダリングしておける静的なので、動的なところだけを部分的に(Partial)Hydrationしたい、JS減らしたいって思っても、それができない。

あとはそのHydrationをするタイミングも、物によってはページロード時である必要はなかったりもする。

  • スクロールされて画面表示されたら
  • 幅狭い端末だったら
  • etc..

そういう意味で、遅延(Lazy)Hydrationできると、なお嬉しいって感じ。

なんでもSPAにするんじゃねぇ!について

基本的には同意する。

SPAじゃなくてよいものをSPAにしないというのは、ちゃんと技術選定・設計するということで、やって当たり前。思考停止でなんでもSPAにするのはダメ以前に問題外で、エンジニアならちゃんとエンジニアリングしないといけない。

というかフォーマットはなんであれ、キャッシュできない巨大なファイルをクライアントに送るのは避けたい。
後のDXに影響を与えかねない一枚岩のコードベースになる可能性があるのも怖い。

ただしその帰着として「ぜんぶSSRにしろ」っていうのは、ちょいと極論過ぎやしませんかね・・ってスタンスです。

じゃあどうするか。

そういえばJamstackっていうのが

(Jamstackの定義も、いつまでたっても人によるけどもさておき)

事前に生成できるものを生成しておくというのは、どんな構成であろうともやればよい。静的コンテンツは正義。

ただ、更新頻度によってはそれが現実的に叶わないというのもわかってて、サイト・アプリのすべてをクラシックなJamstackとして事前レンダリングで構成できるサイト(notアプリ)は、相当限られると思う。

もしそういう真に静的コンテンツのみで構成されるサイトがあるなら、それこそEleventyとか使ってJSとは無縁の世界に暮せばよい。現世にはないと思ってるけど・・。(WebComponentsでワンチャン!って一瞬思ったけどすぐ考え直した)

てなわけで、「静的な部分もあるけど、動的な部分もあるのだ」っていうのこそ一般的で、そこをどうするか考えるところからっていうケースが多いなーと最近は思う。

そこでMPAですよ

という結論である点は、Ryan氏と同じことを考えていたのだなあと。

  • 事前に生成できるものは静的にしておく
  • ちょっとした動的部分は、できれば部分的にHydrationする
  • 複雑なところはMPAの中でSPAにすればいい

というのが、ちょうどいいかなーと思ってる今日このごろ。

ただまあ実際の案件(特に受託)だと、部分的にHydrationできるような攻めたツールは選べないことが多くて。

  • ???「初期の開発はお願いしたいけど、その後は内製したいから、とりあえずReact(Next)でお願いします」
  • わたし「アッ、はい・・(ここに思い浮かんだ言葉を入れてね)」

というわけでなので、「Nextをexport専用のbetter webpackとして使ってMPAにしつつ、せめて中身はPreactにすることで、フルHydrationすることになってもちょっと軽い」っていう妥協点が、自分の中での鉄板な選択肢になってる感じはある。

Astro〜はやくきてくれ〜。

やっぱりSSRはナシなのか

そういえば昔、SSRなんかいらないかもっていう記事を書いたことがあった。

でもあれは、

  • SSR自体が不要というわけではない
  • 銀の弾丸ではないので、誰しもが飛びつくものではないということ
  • そのコストを承知の上で採用するのは構わない

というのが主旨であって、そのスタンスは今も変わってない。自分のこれまでの仕事でSSRの必要性に駆られたことがないがゆえのバイアスは否めないとも思うけど。

とことんパフォーマンスを追求するなら、部分的HydrationよりもJS不要のフルSSRのほうがパフォーマンスがいいのは自明なので。運用の難易度は高いとしても。

ただそのあとでやっぱHydrationしたい・・となると、そういう仕組みからまた必要になるわけで、その結果eBayみたいなところが自社用にMarkoを作るのは合点がいくなーって思った。

Building JavaScript Frameworks to Conquer eCommerce - DEV Community

あとNextとかSvelteKitとかは、初回だけSSRしつつあとはSPAを返すデザインなので、ここでいうフルSSRとはまたちょっと違うよなーとも。

そのあたりのSSRよもやまについては、同じくRyan氏のこの記事シリーズが示唆に富んでてよかった。

Server Rendering in JavaScript Series' Articles - DEV Community

「我々はJSなんか使わん!フルSSRで最軽量最速を目指すんや!」ってなったら、今だとやっぱCDN EdgeのWorkerでやるのが理想なんかな?Cloudflare Workersの`HTMLRewritter`とか、ばっちりその用途っぽいし。

そこまで攻めきれない、SSR用サーバーなんかメンテしたくない(できない)勢としては、やはり事前レンダリング+(部分的)HydrationなMPAっていう構成が、あらゆるコスパがよくてしっくりきてる。

はてさて、この先はどうなることやら。