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

React 中的 SSR 深度探讨

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

为什么无 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 深度探讨” 的相关文章

git的几种分支模式

编写代码,是软件开发交付过程的起点,发布上线,是开发工作完成的终点。代码分支模式贯穿了开发、集成和发布的整个过程,是工程师们最亲切的小伙伴。那如何根据自身的业务特点和团队规模来选择适合的分支模式呢?本文分享几种主流 Git 分支模式的流程及特点,并给出选择建议。分支的目的是隔离,但多一个分支也意味着...

neovim 0.9在win下配置 python开发环境

初级的一些配置点击下面链接查看neovim安装插件管理器neovim常用快捷键neovim python开发环境简易配置方法 (需要手动键入命令行 运行python)安装neovim python的模块pip install pynvim pip install jedi pip install n...

学前端,这30个CSS选择器,你必须熟记

你学会了基本的id,class类选择器和descendant后代选择器,然后就觉得完事了吗?如果这样,你就会错过许多灵活运用CSS的机会。虽然本文提到的许多选择器都属于CSS3,并且只能在现代的浏览器中使用,但学会这些是大有好处的。什么是CSS选择器呢?每一条css样式定义由两部分组成,形式如下:[...

基于 vue3.0 小程序拖拽定制

今天给大家分享一个使用Vue3编写的自由DIY小程序页面。mbDIY 一款基于vue3.x构建的可拖拽定制小程序模板。支持新建页面、自由拖拽模块、复制/移动、自定义模块样式等功能。整个项目分为页面、模块、控件三大部分。模块里面的组件可拖拽至主面板区,编辑后保存即可预览效果。快速安装# 克隆项目 gi...

TDesign企业级开源设计系统越发成熟稳定,支持 Vue3 / 小程序

TDesing 发展越来越好了,出了好几套组件库,很成熟稳定了,新项目完全可以考虑使用。早在2021年,腾讯的 TDesing 刚发布不久,我就写了一篇简短的文章来介绍,当时主要关注的是 TDesign 的 Vue 组件库和用来搭建 admin 后台系统的实用性。虽然当时看起来不错,但还处于测试版,...

Ruoyi-vue第五十二章:Uniapp小程序配置tabbar底部导航栏

一、功能实现效果如下图底部的tabbar二、uniapp的tabBar如果应用是一个多 tab 应用,可以通过 tabBar 配置项指定一级导航栏,以及 tab 切换时显示的对应页。在 pages.json 中提供 tabBar 配置,不仅仅是为了方便快速开发导航,更重要的是在App和小程序端提升性...