Back

Preactによるサーバーサイドレンダリング

Preactによるサーバーサイドレンダリング

ReactとViteでアプリを構築した経験があれば、クライアントサイドレンダリングについてはすでに理解しているはずです。ブラウザがJavaScriptバンドルをダウンロードし、実行してDOMを構築します。これは機能しますが、低速な回線のユーザーはバンドルが読み込まれる間、真っ白な画面を眺めることになります。Preact によるサーバーサイドレンダリング (SSR) は、サーバーから表示可能なHTMLを送信することでこの問題を解決します。Preactのランタイムフットプリントが小さいことから、パフォーマンスを重視するアプリケーションにとって、このトレードオフは特に魅力的です。

本記事では、Preact SSRの仕組み、関連するツール、そしてハイドレーションがどのようにサーバーレンダリングされたHTMLをインタラクティブなアプリへと接続するかを解説します。

重要なポイント

  • Preact SSRは、コンポーネントツリーをサーバー上でHTML文字列にレンダリングし、JavaScriptが実行される前にユーザーへ表示可能なコンテンツを届けます。
  • preact-render-to-string パッケージは、同期・非同期・ストリーミングのレンダリングAPIを提供し、さまざまなアプリケーションのニーズに対応します。
  • ハイドレーションは、render ではなく hydrate を用いてサーバーレンダリングされたHTMLにインタラクティブ性を付与し、既存のDOMを維持します。
  • @preact/preset-vite を利用したViteベースのワークフローは、SSRをサポートする新規Preactプロジェクトのモダンな標準です。
  • ハイドレーション不一致を防ぐため、サーバーとクライアントの出力を一貫させましょう。サーバー側のレンダリング中にタイムスタンプ、ランダムID、window アクセスを使用してはいけません。

Preact SSRとは何か、なぜ重要なのか?

SSRでは、ブラウザが何かを受信する前に、コンポーネントツリーがサーバー上でHTML文字列としてレンダリングされます。ユーザーは即座にコンテンツを目にすることができ、JavaScriptはバックグラウンドで読み込まれ、その後インタラクティブ性が付与されます。

実際的なメリットは以下のとおりです:

  • 体感ロード時間の短縮 — 最初のHTTPレスポンスとともに表示可能なコンテンツが届きます。
  • SEOの向上 — クローラーはJavaScriptを実行することなく、完全に構築されたHTMLを読み取れます。
  • 耐障害性 — クライアントサイドのJSが失敗したり遅延したりしても、ページは閲覧可能なままです。

Preactはランタイムが小さくオーバーヘッドが最小限であるため、SSRに特に適しています。

中核となるツール: preact-render-to-string

Preact SSRは、サーバーサイドレンダリングを担当する独立したパッケージである preact-render-to-string に依存します。

npm install preact-render-to-string

同期レンダリング

非同期の依存関係を持たないコンポーネントの場合、renderToString はコンポーネントツリーを一度にHTMLへ変換します:

import { renderToString } from 'preact-render-to-string';

const App = () => <h1>Hello from the server</h1>;
const html = renderToString(<App />);
// → <h1>Hello from the server</h1>

非同期レンダリング

コンポーネントがデータをフェッチする場合や、遅延ロードされたチャンクとともに Suspense を使用する場合は、代わりに renderToStringAsync を使用します。これは、Promiseと非同期レンダリング処理の完了を待ってから、最終的なHTML文字列を返します。

import { renderToStringAsync } from 'preact-render-to-string';

const html = await renderToStringAsync(<App />);

ストリーミング

大規模なページの場合、ストリーミングAPIを使うと、各セクションがレンダリングされるたびに、HTMLをチャンク単位でブラウザに送信できます。renderToPipeableStream はNode.jsのストリームを対象とし、renderToReadableStream はCloudflare Workers、Deno、Bunなど、Web Streams APIを使用する環境を対象としています。ストリーミングは、レンダリングが完全に完了するのを待たずに Time to First Byte (TTFB) を改善します。

Preactのハイドレーション: HTMLとインタラクティブ性の接続

静的HTMLを送信するだけでは仕事の半分にすぎません。ページをインタラクティブにするには、Preactが既存のDOMを ハイドレート する必要があります。つまり、ゼロから再レンダリングすることなく、イベントリスナーと状態を付与する処理です。

import { hydrate } from 'preact';
import { App } from './app.js';

hydrate(<App />, document.getElementById('root'));

DOMがすでにサーバーによって生成されている場合は、render の代わりに hydrate を使用します。render を使うとサーバー側のHTMLが破棄されて再構築されてしまい、SSRの目的が損なわれてしまいます。

ハイドレーション不一致 は、サーバーレンダリングされたHTMLがクライアントでレンダリングされるものと一致しない場合に発生します。よくある原因として、サーバー側のレンダリング中にタイムスタンプやランダムIDを生成したり、window を読み取ったりすることが挙げられます。これを防ぐには、サーバーとクライアントのコンポーネントロジックを一貫させましょう。

Preact Vite SSR: モダンなセットアップ

新規プロジェクトには、Viteベースのワークフローが実用的な標準です。Viteの SSRガイド では、デュアルビルドパターン (サーバーエントリ向けのビルドとクライアントバンドル向けのビルドの2つを行う方式) が解説されています。Preactは、JSX、エイリアス、Preact固有のセットアップを処理する @preact/preset-vite によってシームレスに統合できます。

より構造化された出発点を求めるチームには、Preact Viteプロジェクト専用に設計された軽量なルーティングおよびプリレンダリングユーティリティである preact-iso が便利です。

留意すべき点

Preact SSRはReact SSRと概念的な共通点を持ちますが、すべての実装詳細が同一というわけではありません。React固有のSSR APIの中には直接の同等物が存在しないものもあり、Preactを取り巻くエコシステムはより小規模です。これは、サイズとパフォーマンスの利点に対する妥当なトレードオフと言えます。

確実に機能するパターンは次のとおりです: レンダリング前にデータをフェッチし、propsとして渡し、サーバー上で文字列へレンダリングし、クライアント側でハイドレートする。ここから始め、必要が生じてからストリーミングやより高度なレンダリングパターンを取り入れていきましょう。

まとめ

Preact SSRは、より大規模なフレームワークのオーバーヘッドなしに、サーバーレンダリングされたアプリケーションを構築するための、軽量でパフォーマンス重視のアプローチを提供します。サーバー側のレンダリングに preact-render-to-string、クライアント側のアクティベーションに hydrate、ビルドパイプラインにViteを組み合わせることで、迅速にリリースでき、理解しやすいセットアップが得られます。基本的なレンダリングとハイドレーションのフローから始め、サーバーとクライアントの出力を一致させ、アプリケーションの規模が正当化するまでストリーミングには手を出さないようにしましょう。

FAQ

バンドルサイズ、コールドスタートのパフォーマンス、エッジデプロイメントが、Reactの広範なエコシステムへのアクセスよりも重要な場合は、Preact SSRを選びましょう。Preactの小さなランタイムは、コンテンツ中心のサイト、マーケティングページ、Workerベースのデプロイメントに最適です。React固有のライブラリに依存していたり、PreactがサポートしないReact Server ComponentsなどのAPIが必要な場合は、React SSRを使い続けてください。

同じpropsに対して、サーバーとクライアントが同じ出力をレンダリングするようにします。初回レンダリング時には、Date.nowやMath.random、windowやlocalStorageなどブラウザ専用のグローバルといった非決定的な値は避けましょう。クライアント専用のコンテンツが必要な場合は、サーバー側では安定したプレースホルダーをレンダリングし、ハイドレーション完了後にエフェクト内で更新してください。

多くのReactライブラリは、ReactのインポートをPreactの相当物にマッピングするpreact/compatエイリアスを通じて動作します。ただし、React Server Componentsやレンダリング内部などReact固有のSSR機能に依存するライブラリは、正しく動作しない可能性があります。SSRパイプラインに組み込む前に、各依存関係をテストしましょう。

おそらくありません。ストリーミングが効果を発揮するのは、ページが大規模であったり、データ量が多かったり、異なる速度で解決されるセクションがある場合です。一般的な小規模サイトでは、renderToStringやrenderToStringAsyncで十分高速に結果が得られるため、ストリーミングは測定可能なメリットなしに複雑さを増すだけになります。まずは同期または非同期のシンプルなAPIから始め、TTFBが実際にボトルネックとなった場合にのみストリーミングを採用しましょう。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay