Tech blog

Deno DeployでReactコンポーネントをSSRする

asyncコンポーネント、SuspendコンポーネントもSSRできてしまったので、ReactDOMすげーなという気持ちです。

どうやる

結論から言うと、 renderToReadableStream を使えば、SSRできてしまうし、Suspendコンポーネントを使ったロード画面もできてしまうという、簡単さでした。

具体的なコード

まずは、一番外側の部分の実装はこうなります。 ただし、Appコンポーネントは定義済みとします。また、useStateなどの状態は持っておらず、あくまでSuspenseやasyncコンポーネントなどをもとに、静的なHTMLが生成されるだけのコンポーネントだとします。

驚くほど簡単ですよね。

import React, { Suspense, use } from "https://esm.sh/react";
import { renderToReadableStream } from "https://esm.sh/react-dom/server";

Deno.serve(async () => {
  const html = await renderToReadableStream(<App />);
  return new Response(html, { headers: { "content-type": "text/html" } });
});

大事なのは、 renderToReadableStream で、こいつが内部でSuspenseやasyncコンポーネントをええ感じにしてくれているのでした。

asyncコンポーネントはなんとなくわかるが、Suspenseはどうなっているんだ

Suspenseは非同期のデータ取得が完了したタイミングでそのHTMLタグを書き換える処理が行われます。

つまり、ストリームで送られてくるので、そのようなことが実現できてしまうわけです。

  1. Suspenseのfallbackの部分を使ってコンポーネントを描画し、HTMLを生成しストリームとして返す
  2. ブラウザーはストリームから受け取ったところまでを描画する(この時点でHTMLがロード中の表示のまま正しく描画されています)
  3. Suspenseの待ち処理が終わったら、Suspenseの中のほんとのHTMLをストリームで返す(hidden属性を付けて非表示状態にしておく)
    • こうすることで、ロード中のHTMLとSuspenseの中のHTMLが別々の場所に描画されている状態になる
  4. 追加でscriptをストリームで返す
    • scriptでは、元のHTMLのSuspense fallbackの箇所を取り除き、hidden属性を付けてあったHTMLをfallbackの場所に差し込むようなJSが書かれている
    • その結果、Suspenseの中身が正しく描画される!

おもろーって感じですね。

Deno Deployでなくても動きますが、無料でSSRできるのは強みだと思うので、おすすめです。

サンプルはこちらで動きをみることができます。通信内容を見てみるといいですね。

https://late-weasel-35.naoki-tomita.deno.net/