Notes / Web技術

React Islandは本当に必要か。小さな図解のための180KB

Astro Islandsは必要な部分だけJSを動かせる仕組みです。一方で、何気なくReactをインポートすると、たった一つのインタラクティブな図解のためにReact + ReactDOMのバンドル全体がページに混ざる。「Astroなら速いはず」を裏切られる原因と、React不要時の代替案。

Astro Islands bundle size performance

Astroで小さなインタラクティブ図解を作ろうとして、何気なくReactで書いたら、ページに180KB近いバンドルが乗ってしまった。「Astro Islandsで必要な部分だけJSを動かせるはず」と思っていたのに、思ったほど軽くなりません。

これは、Astro Islandsの「いつロードするか」と「何をロードするか」を混同したことで起きます。

Astro Islandsの仕組みと落とし穴

Astroはデフォルトで HTML を静的生成し、UIフレームワークのクライアントJSを送りません。client:* ディレクティブを付けたコンポーネントだけが、クライアントサイドでハイドレーションされます。

<MyChart client:visible type="ai-workflow" />

client:load client:idle client:visible client:only でハイドレーションのタイミングを選べます。visible ならビューポートに入ったとき、idle ならメインスレッドが空いたとき、only はSSRせずクライアント側だけで描画。

ここで気をつけたいのは、「いつロードするか」は選べても「何をロードするか」は選べないこと。<MyChart client:visible /> のようにReactコンポーネントを一つでも置けば、React + ReactDOMのバンドルがそのページに乗ります。

実測してみる

Astroが生成する dist を確認すると、React Islandがあるページでは次のようなチャンクが含まれます。

チャンクrawgzip推定
Reactクライアント(react-dom含む)182KB58KB
jsx-runtime0.5KB<1KB
自作コンポーネント(例: InteractiveFigure)4KB2KB

「小さなインタラクティブ図解」が4KBでも、その背後のReact + ReactDOMが60KB近く乗ります。同じ図解をAstroのテンプレートと小さなバニラJSで書けば、合計5KBに収まることもある。差は10倍以上。

Reactが要るかを問い直す

「Astro IslandsがあるからなんでもReactで書こう」は、危険です。次の問いで振り分けると、Reactの出番がぐっと絞られます。

状態がページ寿命より長い必要があるか。Yesなら React (useState、context)、Noならテンプレートと一時的な data 属性で十分です。コンポーネント階層が3段以上ある複雑な props と event の流れか。YesならReact、NoならバニラなDOM操作で済みます。大量の再レンダリングが要るか (リスト、テーブル、diff更新)。YesならReact、NoならDOM直接更新で十分です。既存ライブラリがReact前提か。そのライブラリのためにReactを入れるか、それとも代替を探すか判断。

3ノードの図解、フォーム入力欄数個、開閉するドロワー。これらはすべてバニラで書ける範囲です。Reactを入れる根拠は薄い。

React不要時の代替

静的SVG + 小さなバニラJS

「クリックでハイライト切替」「キーボード操作でフォーカス移動」程度なら、SVGに data-* 属性を持たせ、Astroの <script> でイベントリスナーを貼るだけで足ります。

<svg viewBox="0 0 100 60">
  <g data-node="ai" tabindex="0" role="button" aria-pressed="false">
    <circle cx="50" cy="30" r="7" />
  </g>
</svg>

<script>
  document.querySelectorAll('[data-node]').forEach((g) => {
    g.addEventListener('click', () => {
      g.setAttribute('aria-pressed', 'true');
      // ...
    });
  });
</script>

状態は aria-presseddata-active 属性として、DOM側に持たせます。これだけでReactの代替になる。

Preact化

「React APIそのまま使いたいが、バンドルを小さくしたい」ならPreactと preact/compat alias を使います。React APIほぼ互換で、バンドルは10KB前後。Astroの @astrojs/preact インテグレーションを使えば、差し替えだけで動きます。

Web Components / Lit

将来React以外のフレームワークでも再利用する可能性があるなら、Web Components化が選択肢。Litを使えば5KB程度です。学習コストはやや高め。


Astro Islandsは「JavaScriptを必要な部分だけに絞る」仕組みであって、「Reactを自由に使っていい免罪符」ではありません。そのコンポーネントに本当にReactが必要か、毎回問い直す。静的SVGとバニラJSで書けるケースが、思ったより多いです。バンドル60KB削減は、モバイルのLighthouse Performanceスコアに直結します。

Webパフォーマンス改善のご相談はお問い合わせから、業務領域の詳細はServicesへ。