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

不在伺服器端轉譯,但還是想加快 React 網站的效能?試試預先算繪!

react-snap 是第三方程式庫,可將網站上的網頁預先轉譯成靜態 HTML 檔案。這可以縮短應用程式的首次繪製時間。

在模擬的 3G 連線和行動裝置上載入預先轉譯的相同應用程式,如下所示:

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

這種報表有哪些優點?

大型單頁應用程式的主要效能問題,在於使用者需要等待組成網站的 JavaScript 套件完成下載,才能看到實際內容。套件越大,使用者須等待的時間就越長。

為解決這個問題,許多開發人員會採用在伺服器上算繪應用程式的方法,而不只是在瀏覽器上啟動應用程式。每次網頁/路徑轉換時,系統都會在伺服器產生完整的 HTML 並傳送至瀏覽器,這可以減少第一次繪製的時間,但代謝時間會較慢。

「預先轉譯」是比伺服器轉譯更為複雜的技術,但也提供改善應用程式中的首次繪製時間的方法。無頭瀏覽器或沒有使用者介面的瀏覽器,可用來在建構時間產生每個路徑的靜態 HTML 檔案。如此一來,這些檔案可以連同應用程式所需的 JavaScript 組合一起傳送。

反應

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 酬載的外觀,請按一下 HTML 要求的網址,然後在 Chrome 開發人員工具中按一下「Previews」分頁標籤。

前後比較。「後鏡頭」部分顯示內容已轉譯完畢。

未設定樣式的 Flash 內容

雖然靜態 HTML 現在可以幾乎立即轉譯,但預設仍保持未設定樣式,這可能會導致系統顯示「未設定樣式的內容 (FOUC)」的問題。使用 CSS 內建 JavaScript 程式庫產生選取器時,這項做法會特別明顯,因為 JavaScript 套件必須先執行完畢,才能套用任何樣式。

為了避免發生這種情形,您可以直接在 HTML 文件的 <head> 中內嵌「關鍵」CSS,或是初始頁面轉譯所需的最低 CSS 數量。react-snap 會使用另一個第三方程式庫 (minimalcss),擷取不同路徑的任何重要 CSS。如要啟用這項功能,請在 package.json 檔案中指定以下內容:

"reactSnap": {
  "inlineCss": true
}

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

前後比較。「後拍」顯示內容已轉譯並套用樣式,因為內嵌重要的 CSS。

結語

如果應用程式中不是伺服器端算繪路徑,請使用 react-snap 向使用者預先算繪靜態 HTML。

  1. 將其安裝為開發依附元件,並且先使用預設設定開始執行。
  2. 如果網站適用實驗性的 inlineCss 選項,請使用該選項內嵌重要的 CSS。
  3. 如果您在任何路徑中的元件層級使用程式碼分割功能,請小心不要向使用者預先算繪載入狀態。react-snap README 會進一步說明。