使用 react-snap 预渲染路由

不是服务器端渲染,但仍然想提高 React 网站的性能?试试预渲染!

react-snap 是一个第三方库,可以将你网站上的页面预渲染为静态 HTML 文件。这可以提高你应用中的首次绘制时间。

这是一个比较:在模拟 3G 连接和移动设备上加载的,使用和不使用预渲染的同一个应用

A side by side loading comparison. The version using pre-rendering loads 4.2 seconds faster.

为什么这很有用?

大型单页应用的主要性能问题是用户需要等待构成网站的 JavaScript 包完成下载,才能看到任何实际内容。包越大,用户等待的时间就越长。

为了解决这个问题,许多开发者采取在服务器上渲染应用的方法,而不是仅仅在浏览器中启动它。每次页面/路由转换时,完整的 HTML 都在服务器上生成并发送到浏览器,这虽然减少了首次绘制时间,但代价是首次字节到达时间 (Time to First Byte) 较慢。

预渲染 是一种独立的技术,它比服务器渲染复杂性更低,但也提供了一种提高你应用中首次绘制时间的方法。在构建时,使用无头浏览器(或没有用户界面的浏览器)来生成每个路由的静态 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 请求的 URL,然后单击 Chrome DevTools 中的 Previews 选项卡。

A before and after comparison. The after shot shows content has rendered.

未设置样式的闪烁内容

虽然静态 HTML 现在几乎立即渲染,但默认情况下它仍然是未设置样式的,这可能会导致出现“未设置样式的闪烁内容” (FOUC) 问题。如果你使用 CSS-in-JS 库来生成选择器,这将尤其明显,因为 JavaScript 包必须完成执行后才能应用任何样式。

为了帮助防止这种情况,可以将关键 CSS(或初始页面渲染所需的最小 CSS 量)直接内联到 HTML 文档的 <head> 中。react-snap 在底层使用另一个第三方库 minimalcss 来提取不同路由的任何关键 CSS。你可以通过在你的 package.json 文件中指定以下内容来启用此功能

"reactSnap": {
  "inlineCss": true
}

现在查看 Chrome DevTools 中的响应预览将显示内联了关键 CSS 的样式化页面。

A before and after comparison. The after shot shows content has rendered and is styled because of inlined critical CSS.

结论

如果你没有在应用程序中进行服务器端渲染路由,请使用 react-snap 为你的用户预渲染静态 HTML。

  1. 将其作为开发依赖项安装,并从默认设置开始。
  2. 如果实验性的 inlineCss 选项适用于你的网站,请使用它来内联关键 CSS。
  3. 如果你在任何路由内的组件级别上使用代码拆分,请注意不要向用户预渲染加载状态。react-snap README 更详细地介绍了这一点。