リアクション スナップを使用したプリレンダリング ルート

サーバーサイド レンダリングを使用していないが、React サイトのパフォーマンスを向上させたい場合は、プリレンダリングをお試しください。

react-snap は、サイト上のページを静的 HTML ファイルにプリレンダリングするサードパーティ ライブラリです。これにより、アプリの First Paint の時間を改善できます。

以下に、シミュレートされた 3G 接続とモバイル デバイスで、同じアプリをプリレンダリングした場合としない場合の比較を示します。

横並びでの読み込みの比較。プリレンダリングを使用したバージョンは、読み込み時間が 4.2 秒速くなりました。

なぜこれが有用なのでしょうか

大規模なシングルページ アプリケーションにおける主なパフォーマンスの問題は、サイトを構成する JavaScript バンドルのダウンロードが完了するまでユーザーが実際のコンテンツを表示できないことです。バンドルが大きいほど、ユーザーの待ち時間が長くなります。

この問題を解決するために、多くのデベロッパーは、ブラウザ上でアプリケーションを起動するだけではなく、サーバー上でアプリケーションをレンダリングするアプローチを採用しています。ページまたはルートが遷移するたびに、サーバー上で完全な HTML が生成され、ブラウザに送信されます。これにより、First Paint の時間が短縮されますが、その分、最初のバイトまでの時間が長くなります。

プリレンダリングは、サーバー レンダリングほど複雑ではない別の手法ですが、アプリケーションの First Paint 時間を改善する手段でもあります。ヘッドレス ブラウザ(ユーザー インターフェースのないブラウザ)を使用して、ビルド時にすべてのルートの静的 HTML ファイルを生成します。これらのファイルは、アプリケーションに必要な JavaScript バンドルと一緒に配布できます。

リアクション スナップ

react-snap は、Puppeteer を使用して、アプリケーション内の異なるルートのプリレンダリングされた HTML ファイルを作成します。まず、開発用の依存関係としてインストールします。

npm install --save-dev react-snap

次に、package.jsonpostbuild スクリプトを追加します。

"scripts": {
  //...
  "postbuild": "react-snap"
}

これにより、アプリケーションの新しいビルドが作成されるたびに(npm build)、react-snap コマンドが自動的に実行されます。

最後に必要なのは、アプリケーションの起動方法の変更です。src/index.js ファイルを次のように変更します。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
const rootElement = document.getElementById("root");

if (rootElement.hasChildNodes()) {
  ReactDOM.hydrate(<App />, rootElement);
} else {
  ReactDOM.render(<App />, rootElement);
}

ReactDOM.render を使用してルート React 要素を DOM に直接レンダリングするのではなく、子ノードがすでに存在するかどうかをチェックし、HTML コンテンツがプリレンダリングされた(またはサーバーでレンダリングされた)かどうかを判断します。その場合は ReactDOM.hydrate を使用して、HTML を新規作成するのではなく、すでに作成済みの HTML にイベント リスナーをアタッチします。

アプリケーションをビルドすると、クロールされるルートごとにペイロードとして静的 HTML ファイルが生成されます。HTML リクエストの URL をクリックし、Chrome DevTools で [プレビュー] タブをクリックすると、HTML ペイロードがどのように表示されるかを確認できます。

導入前と導入後の比較。アフターショットは、コンテンツがレンダリングされたことを示します。

スタイルが設定されていないコンテンツのフラッシュ

静的 HTML はほぼ即座にレンダリングされるようになりましたが、デフォルトではスタイルが未設定のままであるため、「スタイルが設定されていないコンテンツのフラッシュ」(FOUC)が表示されるという問題が発生する可能性があります。これは、CSS-in-JS ライブラリを使用してセレクタを生成している場合に特に顕著になります。これは、スタイルを適用する前に JavaScript バンドルの実行が終了する必要があるためです。

これを防ぐために、クリティカルな CSS、つまり最初のページをレンダリングするために最低限必要な CSS を、HTML ドキュメントの <head> に直接インライン化できます。react-snap は、内部で別のサードパーティ ライブラリ minimalcss を使用して、異なるルートの重要な CSS を抽出します。これを有効にするには、package.json ファイルで次のように指定します。

"reactSnap": {
  "inlineCss": true
}

Chrome DevTools のレスポンス プレビューを見ると、重要な CSS がインライン化されたスタイル付きページが表示されています。

導入前と導入後の比較。アフターショットでは、コンテンツがレンダリングされ、クリティカルな CSS がインライン化されているためにスタイル設定されていることがわかります。

おわりに

アプリケーションでサーバー側のレンダリング ルートを使用しない場合は、react-snap を使用して静的 HTML をユーザーに事前レンダリングします。

  1. これを開発環境の依存関係としてインストールし、デフォルト設定をそのまま使用します。
  2. サイトに適している場合は、試験運用版の inlineCss オプションを使用して、重要な CSS をインライン化します。
  3. ルート内のコンポーネント レベルでコード分割を使用する場合は、読み込み状態をユーザーにプリレンダリングしないように注意してください。詳しくは、react-snap README をご覧ください。