使用因應回應預先算繪路徑

您不使用伺服器端算繪,但仍想加快 React 網站的效能嗎?歡迎試用預先算繪功能!

react-snap 是第三方程式庫,可將網站上的網頁預先轉譯為靜態 HTML 檔案。這麼做可以改善應用程式中的第一次繪圖時間。

以下比較在模擬 3G 連線和行動裝置上,有無預先算繪功能的相同應用程式:

並排載入比較。使用預先算繪功能的版本載入速度快了 4.2 秒。

這種報表有哪些優點?

大型單頁應用程式的主要效能問題,是使用者必須等待組成網站的 JavaScript 套件完成下載,才能查看任何實際內容。套件的大小越大,使用者就必須等待越久的時間。

為解決這個問題,許多開發人員會在伺服器上轉譯應用程式,而非只在瀏覽器上啟動應用程式。每次轉換頁面/路徑時,系統都會在伺服器上產生完整的 HTML 並傳送至瀏覽器,這樣可以減少首次繪製時間,但也會降低第一個位元組時間的成本。

預先算繪是一種較不複雜的獨立技術,可改善應用程式中的首次顯示時間。在建構時間期間,系統會使用無頭瀏覽器 (或沒有使用者介面的瀏覽器) 產生每個路徑的靜態 HTML 檔案。接著,這些檔案可與應用程式所需的 JavaScript 套件一併發布。

react-snap

react-snap 會使用 Puppeteer,在應用程式中為不同路徑建立預先算繪的 HTML 檔案。首先,請將其安裝為開發依附元件:

npm install --save-dev react-snap

接著,請在 package.json 中加入 postbuild 指令碼:

"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 要求的網址,然後點選 Chrome 開發人員工具中的「Previews」分頁,查看 HTML 酬載的樣貌。

前後對照。後續畫面顯示內容已算繪。

未設定樣式的內容閃爍

雖然現在靜態 HTML 幾乎會立即轉譯,但根據預設仍會保持未設定樣式,這可能會導致顯示「未設定樣式的內容閃現」(FOUC) 的問題。如果您使用 CSS-in-JS 程式庫產生選取器,就特別需要留意這一點,因為 JavaScript 組合必須先完成執行,才能套用任何樣式。

為避免這種情況,您可以將重要 CSS 或初始網頁轉譯所需的 CSS 最小數量,直接內嵌至 HTML 文件的 <head>react-snap 會在幕後使用另一個第三方程式庫 minimalcss,為不同的路徑擷取任何重要 CSS。您可以在 package.json 檔案中指定以下內容,即可啟用此功能:

"reactSnap": {
  "inlineCss": true
}

在 Chrome 開發人員工具中查看回應預覽畫面,現在會顯示內嵌重要 CSS 的樣式網頁。

前後對照。下方圖中顯示的內容已經過了內嵌的重要 CSS,並經過調整。

結論

如果您未在應用程式中使用伺服器端轉譯路徑,請使用 react-snap 為使用者預先轉譯靜態 HTML。

  1. 將其安裝為開發依附元件,並僅從預設設定開始。
  2. 如果網站可以正常運作,請使用實驗性的 inlineCss 選項來內嵌重要的 CSS。
  3. 如果您在任何路徑中使用元件層級的程式碼分割功能,請小心不要向使用者預先轉譯載入狀態。詳情請參閱 react-snap README