免费不用抽的ssr,不进来看看么

服务端渲染大揭秘

前言

还在替弟弟学业操心的我听到一声:SSR!姐我 SSR 了!javascript

我:???css

???

小学生都会 SSR 了?让我清醒一下。html

哦不,是这个啊~~前端

不知火

忙于 coding 的你必定据说过 SSGSSR 这些名词,到底是啥一块儿来揭秘。java

那什么是 SSR?Super Super Rare?

SSG 全名 Static-Site Generator,静态站点生成,听着名字就晓得是静态的、在构建的时候就生成了,那么想要更新网站内容就要从新构建,这适合企业官网 or 我的博客等,没有频繁更新的诉求。优势想一想就很明显,速度快(连 api 都没确定快了),部署方便(就静态文件丢上去就好使),安全(纯静态也没有 sql 注入).. SSR SSR 全名 Server-side rendering (SSR),服务端渲染,node

CSR 的优势很明显,那缺陷也很明显(这不是废话),即便你作了 dynamic importsplit chunk, 但也免不了 bundle,一个作了 3 年的大应用 bundle 大小 1m 是很常见的,浏览器首次构建 HTML 拿到的是空的,要等 js 执行完了再开始动态渲染改变 DOM 树,这个期间还会请求 api,据获取数据将数据渲染到页面,完成显示,想一想时间就很长。 因为服务器(针对任何页面)提供的初始 HTML 不包含任何特定于应用程序的 HTML,搜索引擎将该网站视为空白,没有任何内容。所以,尽管你的网站有巨大的流量或相关内容,但它可能不会出如今搜索结果的顶端。react

大体脑海里知道 SSR 快,究竟什么快? 若是在服务器上执行 jsHTML 在服务端就能够装配好了,返回给浏览器渲染,页面就能够有初步的展现,可是在服务端是没有 Window 无法进行绑定的,所以在客户端还要执行一遍脚本,执行生命周期方法,对事件就行绑定,对 DOM 进行 diff,在这段事件完成以前, 服务端和客户端要执行一套代码,就是同构,以 react 为例,hydrate 不会再从新渲染 HTML.webpack

要解决的问题

同构(Isomorphic rendering),就是服务端和客户端一套代码,服务端去渲染,客户端来负责交互。nginx

路由的同构与数据预取web

客户端使用 BrowserRouter,服务端使用 staticRouter,在 node 端没有 history 对象,只是根据请求的路由返回匹配的 React.createElementmatchRoutes 方法实现路由匹配 数据的预取 声明路由的时候把数据请求方法关联到路由中,好比定一个 loadData 方法,而后在查找到路由后就能够判断是否存在 loadData 这个方法。

// routes.ts
const routes = [
  {
    path: "/",
    component: loadable(() => import("./Com")),
    loadData: () => getData(),
  },
];
const loadData = () => {
  const promises: Promise<unknown>[] = [];
  routes.some((route) => {
    const match = matchPath(ctx.request.path, route);
    // 调用定义的获取数据的方法
    if (match && route.loadData) promises.push(route.loadData());
    return match;
  });
  return Promise.all(promises).then(() => {
    return Promise.resolve(
      <StaticRouter> <App /> </StaticRouter>
    );
  });
};

// 预取的数据写入HTML(ejs)
复制代码

动态加载以及资源获取

使用 loadable 库 主要缘由是获取资源映射,当路由匹配到 key 获取 value 资源 塞到 HTML 返回字符串给客户端。而且 dynamic import 在客户端能够用 React.lazy,但在 18 以前不能用,若是用 React.lazyjs 加载并执行以后才能加载对应页面的 bundle,增长了 TTI(首次交互)的时间,即便 react18 能够了资源映射仍是须要本身来获取( loadable 逃不掉了),@loadable/webpack-plugin 能够打出来资源映射的 map,交给 ChunkExtractor,思路是首先匹配路由,根据匹配到的路由取相应的映射资源,加载资源

// webpack配置
const LoadablePlugin = require('@loadable/webpack-plugin');

module.exports = {
  module: {
    rules: [],
  },
  plugins: [
    new LoadablePlugin(),
    ...
  ],
};
// 资源映射
const statsFile = path.resolve(__dirname, '../dist/asset/loadable-stats.json'); // 上面的loadaer默认打出来这个名字
...
const extractor = new ChunkExtractor({
  statsFile,
  publicPath: '/',
});
// extractor.getLinkTags(), extractor.getStyleTags(), ...
复制代码

渲染同构

React.hydrate 水合 这个 api ,对节点进行对比,客户端执行生命周期方法,不会再从新渲染 HTML,比对客户端和服务端的 HTML 节点作 diff,比对结果不一致的时候,HTML 上的属性不会被替换,会把不同的子节点替换,会抛出指向出错节点 warning,须要手动处理。

WechatIMG106120-tuya

数据同构

吐槽一下比较蛋疼的解释(国内大多数文章会出现的概念),从数据层面,把数据放到 Window 上叫注水,把数据从 Window 取出来叫脱水,属实比较难理解,简单理解就是服务端将数据写到 ejs 的模版里,做为全局变量,服务端就从 Window 上取这个变量,实现数据的同构。

///rendux的数据
// ejs模版
<body>
  <div id="root"><%- html %></div>
  <script type="text/javascript"> window.REDUX_PRELOAD_DATA = <%- preloadState %> </script>
  <%- reload -%>
  <%- scriptTags %>
</body>

// server/app.ts
ejs.renderFile(
    template,
    {
      ...
      // 将preloadState变量写入ejs模版
      preloadState: JSON.stringify(ctx.store.getState()),
      ...
    },
    {},
    (err, str) => {
      ...
    },
  );
});
复制代码

取到路径,key(路由路径) value(页面所需的资源)

loadable 如何知道的页面路径?

const jsx = extractor.collectChunks(reactApp);其实这块是建立 provider,而后下面的 loadable(() => import(''))至关于 consumer

image-20210809230313872

纸巾一擦,咱继续,继续,接着 wu,接着 tiao。

性能监控

监控 nodejs v8 堆内存,内存超出 80%进行服务降级,以及在时间范围内(可能半小时检查一次,试业务状况而定),将不健康的容器部署到其余实例。 process.memoryUsage() 返回一个对象,描述 Node.js 进程的内存使用量(以字节为单位)。

import { memoryUsage } from "process";

console.log(memoryUsage());
// 打印:
// {
// rss: 4935680,
// heapTotal: 1826816,
// heapUsed: 650472,
// external: 49879,
// arrayBuffers: 9386
// }
复制代码
  • heapTotalheapUsed 指的是 V8 的内存使用状况。
  • external 指的是绑定到 V8 管理的 JavaScript 对象的 C++ 对象的内存使用。
  • rssResident Set Size,是进程在主内存设备(即总分配内存的一个子集)中占用的空间量,包括全部 C++JavaScript对象和代码。
  • arrayBuffers 是指为 ArrayBufferSharedArrayBuffer 分配的内存,包括全部 Node.js Buffer。 这也包含在 external 值中。 当 Node.js 用做嵌入式库时,此值多是 0,由于在这种状况下可能不会跟踪 ArrayBuffer 的分配。

抛开一切,咱们用 Next.js 吧

上面提出的问题,Next.js 均可以完美解决,通用级 SSR 解决方案,虽然上面说了一堆原理,但做为企业级解决方案依然不够,Next.js 大量的代码在处理各类兼容性的问题,做为体量较大的react项目,选取通用性方案更为推荐。

混合渲染

咱们在实际业务中经常是部分页面须要 SSR,其他的依旧 CSR。 对于咱们本身搭的简易 SSR 能够在配置白名单,作请求匹配的时候, 位于白名单的吐空的 HTML 字符串(仅有 css、js 资源的);或者在 nginx 一层作拦截,白名单转发到 CSR 的地址。 Next.js 为这种混合渲染提供了更为简单的方式,提供 getStaticProps 静态生成的 api,在这里面的请求会被在构建的时候请求好,写入数据到Window。若是没有 export getServerSideProps 方法,就会默认走 SSG 渲染,getServerSideProps 是只会在服务端执行的 api,所以在静态生成的时候不会导出getServerSideProps,下面是静态生成的代码。

export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  };
}
复制代码

服务降级

node 服务器不健康的时候,达到毫秒级 CSR,即不须要依赖 对于咱们本身搭的框架,可采起 serverclient 分开打包,给 client 打包产出加上 HTML 文件,就能够单独托管了。下图是 nginx 配置样例及优雅降级的原理图,解释下就是用户请求到 nginx,若是服务器正常就会转发到 node 渲染服务器,若是异常返回异常状态码,拦截异常状态码,并重写成 200,转发到 HTML 静态文件服务器。 服务降级

天然而然优雅的服务降级

既然自带 SSG,那么咱们的服务降级即可采起他的静态生成,官方提供 next export 命令,能够直接生成 SSG 产出,将每一个路由都打出一个 HTML 文件,里面会引入所须要的 cssjs,须要注意的是想要一套代码就须要种植环境变量,由于 SSG 要求是不能暴露getServerSideProps,兼容处理代码以下

// .sh
export NEXT_SSG = SSG
// .tsx
let getServerSideProps =
  process.env.NEXT_SSG === "SSG"
    ? undefined
    : async () => {
        const res = await fetch(`url`);
        const post = await res.json();
        return { props: { name: post?.data?.token } };
      };

export { getServerSideProps };
复制代码

将打出来的产出丢到 nginx,以下配置

server {
root /www/data;

    location / {
        try_files $uri ;
    }

}
复制代码

若是取到了返回路径下的 HTML,客户端拿到 HTML 字符串以后再 hydrate,实际上仍是 CSR,实现了不走 node 服务器的优雅降级。

WeChat2a1bf8dbab6316ddef9eeb79e1b15ecd-tuya

自带的性能分析

对于 web 应用的性能分析老是绕不开Web Vitals的几大指标,Next.js Analytics 为咱们提供了很是方便的 api 来获取这些指标数据。 对于部署在托管在Vercel的项目,在其Analytics tab 页签中就能够看到可视化的指标数据。 对于自托管的项目能够经过也是能够进行 web 性能分析的。 仅仅只须要建立一个_app.js 在其中暴露一个名为reportWebVitals的方法。

Next.js 会在完成任何一个指标计算的时候调用该函数。

//_app.js
export function reportWebVitals(metric) {
  console.log(metric);
}
// 打印
// {
// id: "1628518848412-9295257969280",
// label: "web-vital",
// name: "TTFB",
// startTime: 0,
// value: 815.5,
// }
复制代码
  • id:指标惟一的标识符;
  • label: 是指标类型,分别是web-vitalscustom
  • name:指标名称;
  • startTime: 以毫秒为单位,全部记录该指标的时间戳;
  • value: 以毫秒为单位,指标的值或者持续的时间。

web-Vitals

是谷歌提出的用来统一衡量web页面用户体验和质量的指标。Next.js为咱们提供了一下五种指标数据:

  • 首字节时间TTFB
  • 首次内容绘制FCP
  • 衡量加载性能LCP
  • 衡量可交互性FID
  • 衡量视觉稳定性CLS

custom

这是Next.js提供的独有的指标,用来衡量 hydraterender 时间

  • Next.js-hydration:页面开始和完成hydrate所需的时间(以毫秒为单位)
  • Next.js-route-change-to-render:页面在路由改变后到开始渲染的时间(以毫秒为单位)
  • Next.js-render: 路由更改后到页面完成渲染的时间(以毫秒为单位)

经过这个函数咱们就能够建立本身的性能分析报告,这还不香么!

src=http___i0.hdslb.com_bfs_article_2229244e224ca19d6753fe495d37055b48b47d72.jpg&refer=http___i0.hdslb-tuya

其余

动态加载、动态路由匹配等 next10 均已经支持,须要的能够移步文档哦~ www.nextjs.cn/docs/gettin…

试状况选择 serverless

什么是 serverless 呢,广义来讲,自动扩容 按需计费 noops 无需运维符合这些就算是了,原本 serverless 就是很抽象的概念,大体步骤是 SSR 应用放在函数中,serverless 有触发器,选用 http 触发器,触发器中能够添加路由,接收到的路由传递给 Next.js,再返回 HTML 给客户端。

Serverless 能解决什么问题?

Serverless 可使应用在服务端免运维。在没有流量的时候缩容为 0,节省流量。能够节省很多开支。是性价比高的方案。 对于落地页可能在特定状况流量增多,以及边缘服务等,使用 Serverless+SSR 可谓是完美配合~。 啥叫免运维呢,将一个服务 部署在服务商给咱们提供的 运行环境中,不须要关心运维相关的东西,只须要关心业务代码,咱们也不须要维护物理机 虚拟机 之类的 Linux。 各大云服务厂商有封装好的 Next.js 服务,能够简单操做快速部署 Next.js

有须要能够自行搜索哦,就不贴了~

总结

介绍了 ssr、ssg 是啥 解决了什么问题、原理以及性能监控,通用级别 ssr 框架 nextjs 如何作优雅服务降级,(是否是已经跃跃欲试想实操),nextjs 依旧在持续更新中并在几天前发布了 11,支持 module federation,微前端也能够用 nextjs(手动狗头)。

番外

我是萱酱,是个 lo 娘 FE(鼓励师划掉),路过的朋友给个三连叭~你的支持是萱酱创做的动力(说的我都感动了),我会持续更新~

相关文章
相关标签/搜索