本記事は、2022年9月に開催された
古川と申します。@yosuke_
SPAは”見えるようになるまでが遅い”
私が今回お話するのは
もうちょっとざっくり話をすると、ブラウザから何かしらのリクエストが発生してサーバからHTMLが返ってきます。そのときのHTMLにはまだ何も中身が入ってないので、中身のHTMLをもう1回構築するためにJSを取ってきて、JSが読み込まれて、最終的に表示がされて、そのときには操作もできるというのが、シングルページアプリケーションのクラシックな動きですね。
シングルページアプリケーションの問題点としては、見えるようになるまでが若干重たく、遅いです。最初のタイミングでHTMLが返ってきているんだけど、コンテンツがあまり見えなくて、JSが読み込まれて初めて見えるという動きです。そのかわり、この後は速い。これがSPAの一番のポイントですね。つまり、最初は遅いんだけど、その後は速いのが特徴です。
この問題
要は、HTMLを何かしらのタイミングで作っておいて、それを返すということをやれば、どっちもいいとこ取りできるじゃん、という技術です。
MPA技術を使ってSPAを作る ―MPA化するSPA
SSRは普通にMPA
どういうことになってるかというと、何かしらリクエストが走ります。クラシックなSSRを例に挙げると、SSRを実行して、実行が終わったらHTMLが返ります。その時点ではすでに描画されていて、その上でもう1回JSをロードするためにJSを取ってきてロードされます。こんな動き
この時点でだいぶMPAとSPAのいいとこ取りをしていて、MPAの技術であるHTMLをサーバサイドでレンダリングするのと、ロードされた後はSPAの技術でハイブリッドな戦略を取る感じになっています。SPAだったけど、徐々にMPAに近寄っているなと…いう印象を持っていただければいいかなと思います。
ただし、問題点が2つあるんですよ。ひとつは、SSRを実行したときの時間が伸びていること、もうひとつの問題がHTMLが描画されてからJSがロードされるまでの時間です。この2つに時間とコストがかかってしまいます。
問題1 ―SSR実行時の時間とコスト
SSRを実行しているときって、毎回リクエストしてる最中のオンザフライで作るというのはけっこう問題も大きくて、たとえばイベントループを止めちゃうとか、CPUコストが高いとか、結果、サーバからHTMLが返るまでのコストがかかってしまいます。
かといってSSGならどうかというと、SSGは事前に全部作らないといけないので、たとえば30万ページあれば、30万ページ分を一気に作らないといけないので、あまり現実的ではないです。
ではISRはどうかというと、結局これはキャッシュなので、最初にリクエストしたときに作り直す時間がかかってしまいますし、かつ、revalidateと呼ばれるキャッシュを再検証する期間をどうするかというのも問題のひとつなんですが、これを長くすればするだけ反映される時間は遅くなってしまうし、短くすると今度はSSRと同じになってしまう。キャッシュライフサイクルという問題に移し替えればできますが、本質的には変わらないです。ちなみにこれだけだったらCDNとSSRだけでできてしまうので、ISRだからといってソリューションになっているかというと、ワークアラウンドのひとつでしかないです。
(問題1については)
Streaming SSRというのは、リクエストが来てから、SSRでHTMLを返すときに全部一気に作って返すんじゃなくて、でき上がったところからちょっとずつ返す感じです。HTMLの描画が開始されてから終わるまでの体感時間を短縮します。
問題2 ―JSがロードされるまで「見えるのに押せない!」
JSがロードされるまで操作できない - これは見えるのに押せないという問題があるのですが、これが今のところフレームワーク界隈ではホットトピックだなと思っています。LCP
そもそもbundle.
React陣営に関しては、もっと積極的なアプローチを取ろうとしていて、Selective Hydrationを発表しています。2019年のGoogle IOでは、Progressive Hydrationと呼ばれていましたけど、要は部分的に使われているところだけ利用可能にしていくアプローチがあるんじゃない?という話があります。
すべてのコンポーネントに対して一気にHydrateするのではなくて、段階を踏んで少しずつやっていきましょうというのが、Selective Hydrationの流れになっています。こうすることによって、Hydrationもレイアウトも同じように少しずつでき上がったところからやっていくのが良いんじゃない? というのがReactのアプローチです。
ここでちょっと待った! です。Reactはこれで良いと思っているとしてもちょっと待った。
そもそもなんでそんな時間がかかるんですか? という話なんですよね。そもそもなんでそんなにHydrationとかをやるのに時間がかかるんですか? そもそものやり方から見直しませんか?
新しいアプローチ ―AstroとQwik
ここで新しいアプローチを2つ紹介します。まず、そもそもなんでそんなに時間がかかるのっていうと、まずJSが重いからですよね。そしてJSがロードされた後にイベントハンドラやサーバで作られた状態をコンポーネントに登録するのに時間がかかる
1つ目の対抗馬がAstroになります。Astroというのは、Next.
それじゃインタラクションがあるような部分的な更新が必要なところはどうするの? という話なんですが、インタラクションがあるコンポーネントだけを狙って、そこにだけ
あとはエッジに置くこともサポートしているので、SSRで遅いという問題もエッジに置いて、分散されたノードでやればCPUの負荷を分散させることができるんじゃない?という話があります。
Astroの解決策は、SSR遅い問題は
もうひとつの対抗馬のQwikも同じです。QwikはそもそもMPAで、Hydrationなんてそもそもいらないだろという立ち位置です。
(Qwikの)
つまりQwikも、基本的にMPAで、Hydrationしないものです。
この特徴を以下のようにいろいろ書き換えてみたんですけど、何を言いたいかというと、Reactのコンポーネントを書いて、DOM上にイベントハンドラを読み込ませるためのJSを置きます。イベントが起きたらそのタイミングでそのJSを取ってくるみたいなことをします。
なので、押されるまでJSはロードされてないんです。押されて初めて初期起動していく - かなりオピニオンが強いフレームワークです。
というわけでQwikに関してはいろんなことをやっています。
AstroとQwikはどちらかというとSPAを作ってMPAを作っているところですね。このあたりは動きがあるところなので、続きはディスカッションで話させてください。ありがとうございました。