当前位置:首页 > 技术分析 > 正文内容

React 中的 SSR 深度探讨

ruisui883周前 (04-07)技术分析15

为什么无 JavaScript 环境如此重要

我们从无 JavaScript 环境开始讨论。这可能是最令人困惑的缺点。如今谁还会在浏览器中禁用 JavaScript?它几乎在所有地方都是默认启用的,没有它几乎都不会工作,而且大多数人甚至不知道 JavaScript 是什么,更不用说去禁用它了。对吧?

这里的答案在于 “人” 这个词,或者更准确地说,是实际访问你网站的不仅仅是人。两个主要参与者是:

  • 搜索引擎机器人(爬虫)。
  • 各种社交媒体和消息应用的 “预览” 功能。

它们的工作方式大致相似。首先,它们以某种方式获取你网站的 URL。

其次,机器人向服务器发送请求并接收 HTML。

第三,从 HTML 中提取信息并进行处理。搜索引擎提取诸如文本、链接、元标签等信息。基于这些信息,它们形成搜索索引。社交媒体预览功能抓取元标签并创建我们大家都见过的漂亮预览,带有图片、标题,有时还有简短描述。

下载项目并安装依赖项:

npm install

然后构建并启动:

npm run build
npm run start

在主页和设置页面之间。你会看到页面标题随导航变化。

useEffect(() => {
  updateTitle('学习项目:主页');
}, []);

其中,内部代码如下:

export const updateTitle = (text: string) => {
  document.title = text;
};

然而,你还会看到初始加载时标题会短暂 “闪烁”——因为默认标题是 “Vite + React + TS”,这是 index.html 中的标题,也是从服务器接收的标题。

现在,使用 ngrok(或类似的工具)将网站暴露给外界:

ngrok http 3000

尝试在你选择的社交媒体上分享它生成的 URL。在生成的预览中,你会看到旧的 “Vite + React + TS” 标题。没有加载 JavaScript。

虽然,这并不完全适用于某些机器人。大多数流行的搜索引擎确实会等待 JavaScript 加载。例如,谷歌:它解析 “纯” HTML,还将页面放入 “渲染” 队列,在那里它实际上会启动浏览器,加载网站,等待 JavaScript 渲染,然后再次提取所有信息。

因此,如果你的网站:

  • 尽可能快地被搜索引擎发现。
  • 在社交媒体平台上分享。

那么服务器返回 “正确” 的 HTML,包含所有关键信息非常重要。典型的例子包括:

  • 以阅读为主的网站,即各种形式的博客、文档、知识库、论坛、问答网站、新闻机构等。
  • 各种形式的电子商务网站。
  • 落地页。
  • 几乎所有可以在万维网上搜索到的内容。

这并不意味着我们需要抛弃 React。有几个解决方案可以尝试。

服务器预渲染

在学习项目中,它看起来是这样的:

app.get('/*', async (c) => {
  const html = fs
    .readFileSync(path.join(dist, 'index.html'))
    .toString();

  return c.html(html);
});

当服务器收到任何请求时,它只是读取为我们提前生成的 index.html 文件,将其转换为字符串,并将其发送给请求者。

然而,为了解决 “无 JavaScript” 问题,我们现在需要修改代码。

例如,找到现有的标题并将其替换为 “学习项目”:

app.get('/*', async (c) => {
  const html = fs
    .readFileSync(path.join(dist, 'index.html'))
    .toString();

  const modifiedHTML = html.replace(
    'Vite + React + TS',
    `学习项目`,
  );

  return c.html(html);
});

这稍微好一些,但在现实生活中,标题应该随着每个页面而变化:将其保持静态是没有意义的。幸运的是,每个服务器总是确切地知道请求来自哪里。对于我使用的框架(Hono),只需询问 c.req.path 即可提取它。

之后,我们可以根据该路径生成不同的标题:

app.get('/*', async (c) => {
  const html = fs
    .readFileSync(path.join(dist, 'index.html'))
    .toString();

  const title = getTitleFromPath(pathname);

  const modifiedHTML = html.replace(
    'Vite + React + TS',
    `${title}`,
  );

  return c.html(html);
});

其中,在 getTitleFromPath 中可以这样做:

const getTitleFromPath = (pathname: string) => {
  let title = '学习项目';

  if (pathname.startsWith('/settings')) {
    title = '学习项目:设置';
  } else if (pathname === '/login') {
    title = '学习项目:登录';
  }

  return title;
};

还有一件事可以让它更漂亮:在 index.html 文件中,我们可以将原始标题 Vite + React + TS 替换为类似 {{title}} 并将其变成模板。


  
    {{ title }}
  
  ...
;

然后在服务器上这样做:

const modifiedHTML = html.replace('{{title}}', title);

未来,如果需要,我们可以将其转换为任何模板语言。

当然,我们不仅限于 title 标签——我们可以通过这种方式预渲染 中的所有信息。这为我们解决社交媒体预览功能的问题提供了一种相对容易且廉价的方式。

我们甚至可以预渲染整个页面,而不仅仅是元标签!

服务器预渲染的成本

与完全静态的单页应用相比。通过添加一个简单的预渲染脚本,我引入了两个问题。

部署到哪里?

第一个问题是,我现在应该将应用部署到哪里?

现在,我需要一个服务器。

这里有两种最常见的解决方案。

我们可以使用托管提供商的 无服务器函数

无服务器函数的缺点是 “按使用量计费” 部分。网站越受欢迎,使用量超过限制的可能性就越大。

如果你不选择无服务器函数,你可以使用一个实际的服务器 并将其部署到任何云平台。

这种解决方案有其优势。一切都在你的控制之下。从一个解决方案迁移到另一个解决方案不需要代码更改。价格通常更可预测,更简单,而且当使用量增加时更低。

拥有服务器的性能影响

还记得初始加载文章对初始加载性能的影响吗?

如果它作为无服务器函数部署,那么有可能并不算太糟。一些提供商可以在 “边缘” 运行这些函数。

然而,如果我选择了自行管理的服务器,我就没有分布式网络的优势。

在服务器上预渲染整个页面(SSR)

在上面的部分中,我们预渲染了元标签。让我们看看 HTML 页面中由服务器发送的 标签的内容:


  
<script type="module" src="./main.tsx"></script>

还记得客户端渲染的工作原理吗?当脚本下载并处理后,React 获取 “root” 元素并将生成的 DOM 元素附加到其中。那么,如果我返回一个包含一些内容的 div 而不是一个空 div 会怎样?让我们把它变成一个大红块:

大红块

将其添加到 index.html 中,构建项目,启动它,并禁用缓存,放慢 CPU 和网络以提高可见性。

当你刷新页面时,你应该会看到大红块的瞬间闪烁,然后被正常的页面替换。

幸运的是。React 提供了一些方法可以预渲染整个应用。例如,有一个 “renderToString”。

const App = () => 
React 应用
; // 在服务器上的某个地方 const html = renderToString(); // 输出将是
React 应用

由于我们已经在服务器上处理字符串。我需要做的就是将空的 “root” div 替换。让我们试试?

转到 backend/index.ts 并清理我们之前所做的任何修改。找到被注释掉的代码:

// return c.html(preRenderApp(html));

取消注释。重新记录性能。最终结果应该像这样:

FCP 和 LCP 同时发生。在主 React 生成的 JavaScript 触发之前甚至在 JavaScript 加载完成之前。这意味着内容预渲染正在工作!

这就是 SSR 值得追求的地方。

SSR 可能使初始加载变得更糟

不稳定,因为性能方面没有银弹。如果有人说 SSR 将 100% 提高单页应用的初始加载性能,他们就错了。现在你知道网络条件、客户端和服务器端渲染的工作原理,你能想到 SSR 使 LCP 变糟的情况吗?

现在,在启用和不启用预渲染的情况下测量 LCP。

对于我来说,结果是这样的。在不启用预渲染的情况下,即 “单页应用” 模式,LCP 在 2.13 秒左右。在启用预渲染的情况下,即 “SSR” 模式,它在 2.62 秒左右。几乎长了 500ms!

SSR 与前端

浏览器 API 与 SSR

还记得我是如何获取发送到浏览器的 HTML 的吗?我只是用 React 的 renderToString 生成了一个字符串,然后将其注入到另一个字符串中。

那么,浏览器变量调用会怎么样呢? window.location 和 window.history 以及 document.getElementById?没有好消息。window、document 等将变为 undefined。

因此,当 React 尝试渲染一个访问这些变量的组件时,它将因 window 未定义 错误而失败。整个应用将崩溃。

useEffect 与 SSR

在 use-client-router 文件。如果你仔细看,会发现我不必在 useEffect 中检查 typeof window:

useEffect(() => {
  const handlePopState = () => {
    setPath(window.location.pathname);
  };
  window.addEventListener('popstate', handlePopState);
  return () =>
    window.removeEventListener('popstate', handlePopState);
}, []);

这是因为当在服务器上运行(通过 renderToString 等)时,React 不会触发 useEffect。useLayoutEffect 也是如此。这些钩子将在水合发生后在客户端触发。

第三方库

并非所有外部依赖项都支持 SSR。

有些需要在客户端 JavaScript 加载后动态导入。

有些需要从项目中移除并替换为更友好的 SSR 库。

如果非 SSR 的库是项目的核心,比如状态管理解决方案或 CSS-in-JS 解决方案,这将特别痛苦。

例如,尝试在学习项目中某个地方使用 Material UI 图标:

// 例如在 src/App.tsx 中的任何地方
import { Star } from '@mui/icons-material';

function App() {
  // 其余代码相同
  return (
    <>
      ...
      
    
  );
}

重新构建并启动——你应该会看到 SSR 崩溃,显示:

[vite] (ssr) Error when evaluating SSR module @/App: deepmerge is not a function

原文:
https://dev.to/adevnadia/ssr-deep-dive-for-react-developers-542b

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/3329.html

标签: readfilesync
分享给朋友:

“React 中的 SSR 深度探讨” 的相关文章

vue:组件中之间的传值

一、父子组件之间的传值----props/$emit1、父组件向子组件传值--props2.子组件想父组件传值-this.$emit('select',item)二、父组件向下(深层)子组件传值----provide/injectprovide:Object | () => O...

用IDEA开发如何用Git快速拉取指定分支代码?

1,准备空的文件夹,git init2,关联远程仓库,git remote add origin gitlab地址3,拉取远程分支代码,git pull origin 远程分支名再用IDEA打开项目即可...

理解virt、res、shr之间的关系(linux系统篇)

前言想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES和SHR三个重要的指标,他们分别代表什么意思呢?这是本文需要跟大家一起探讨的问题。...

Python 幕后:Python导入import的工作原理

更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)Python 最容易被误解的方面其中之一是import。Python 导入系统不仅看起来很复杂。因此,即使文档非常好,它也不能让您全面了解正在发生的事情。唯一方法是研究 Python 执行 import 语句时幕后发生的事情。注意:在这篇文...

VIM配置整理

一、基本配色set number set showcmd set incsearch set expandtab set showcmd set history=400 set autoread set ffs=unix,mac,dos set hlsearch set shiftwidth=2 s...

全新斯柯达柯珞克Karoq深度评测:大众替代品

“斯柯达柯珞克是一款出色的全能家庭 SUV,具有许多有用的功能”价格36,605 英镑- 49,190 英镑优点方便的 VarioFlex 后排座椅非常适合家庭入住驾驶乐趣缺点保修期短保守的内饰性格比Yeti少结论——斯柯达柯珞克是一辆好车吗?斯柯达柯珞克是在辉煌的七座 斯柯达柯迪亚克之后推出的,因...