使用回应贴靠预渲染路线

未使用服务器端渲染,但仍想提升 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"
}

这样,每当应用的新 build 构建时,系统都会自动运行 react-snap 命令 (npm build)。

您需要执行的最后一项操作是更改应用的启动方式。将 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 自述文件对此进行了更详细的介绍。