还在替弟弟学业操心的我听到一声:SSR!姐我 SSR 了!javascript
我:???css
小学生都会 SSR 了?让我清醒一下。html
哦不,是这个啊~~前端
忙于 coding
的你必定据说过 SSG
、SSR
这些名词,到底是啥一块儿来揭秘。java
SSG
全名 Static-Site Generator
,静态站点生成,听着名字就晓得是静态的、在构建的时候就生成了,那么想要更新网站内容就要从新构建,这适合企业官网 or 我的博客等,没有频繁更新的诉求。优势想一想就很明显,速度快(连 api
都没确定快了),部署方便(就静态文件丢上去就好使),安全(纯静态也没有 sql
注入)..
SSR
全名 Server-side rendering (SSR)
,服务端渲染,node
CSR
的优势很明显,那缺陷也很明显(这不是废话),即便你作了 dynamic import
,split chunk
, 但也免不了 bundle
,一个作了 3 年的大应用 bundle
大小 1m 是很常见的,浏览器首次构建 HTML
拿到的是空的,要等 js
执行完了再开始动态渲染改变 DOM
树,这个期间还会请求 api
,据获取数据将数据渲染到页面,完成显示,想一想时间就很长。 因为服务器(针对任何页面)提供的初始 HTML
不包含任何特定于应用程序的 HTML
,搜索引擎将该网站视为空白,没有任何内容。所以,尽管你的网站有巨大的流量或相关内容,但它可能不会出如今搜索结果的顶端。react
大体脑海里知道 SSR
快,究竟什么快? 若是在服务器上执行 js
,HTML
在服务端就能够装配好了,返回给浏览器渲染,页面就能够有初步的展现,可是在服务端是没有 Window
无法进行绑定的,所以在客户端还要执行一遍脚本,执行生命周期方法,对事件就行绑定,对 DOM
进行 diff,在这段事件完成以前, 服务端和客户端要执行一套代码,就是同构,以 react
为例,hydrate
不会再从新渲染 HTML
.webpack
同构(Isomorphic rendering)
,就是服务端和客户端一套代码,服务端去渲染,客户端来负责交互。nginx
路由的同构与数据预取web
客户端使用 BrowserRouter
,服务端使用 staticRouter
,在 node
端没有 history
对象,只是根据请求的路由返回匹配的 React.createElement
,matchRoutes
方法实现路由匹配 数据的预取 声明路由的时候把数据请求方法关联到路由中,好比定一个 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.lazy
主 js
加载并执行以后才能加载对应页面的 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
,须要手动处理。
数据同构
吐槽一下比较蛋疼的解释(国内大多数文章会出现的概念),从数据层面,把数据放到 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
。
纸巾一擦,咱继续,继续,接着 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
// }
复制代码
heapTotal
和 heapUsed
指的是 V8 的内存使用状况。external
指的是绑定到 V8 管理的 JavaScript
对象的 C++
对象的内存使用。rss
,Resident Set Size
,是进程在主内存设备(即总分配内存的一个子集)中占用的空间量,包括全部 C++
和 JavaScript
对象和代码。arrayBuffers
是指为 ArrayBuffer
和 SharedArrayBuffer
分配的内存,包括全部 Node.js Buffer。 这也包含在 external
值中。 当 Node.js 用做嵌入式库时,此值多是 0,由于在这种状况下可能不会跟踪 ArrayBuffer
的分配。上面提出的问题,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
,即不须要依赖 对于咱们本身搭的框架,可采起 server
和 client
分开打包,给 client
打包产出加上 HTML
文件,就能够单独托管了。下图是 nginx
配置样例及优雅降级的原理图,解释下就是用户请求到 nginx
,若是服务器正常就会转发到 node
渲染服务器,若是异常返回异常状态码,拦截异常状态码,并重写成 200
,转发到 HTML
静态文件服务器。
既然自带 SSG
,那么咱们的服务降级即可采起他的静态生成,官方提供 next export
命令,能够直接生成 SSG
产出,将每一个路由都打出一个 HTML
文件,里面会引入所须要的 css
和 js
,须要注意的是想要一套代码就须要种植环境变量,由于 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
服务器的优雅降级。
对于 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-vitals
和custom
;name
:指标名称;startTime
: 以毫秒为单位,全部记录该指标的时间戳;value
: 以毫秒为单位,指标的值或者持续的时间。是谷歌提出的用来统一衡量web
页面用户体验和质量的指标。Next.js
为咱们提供了一下五种指标数据:
TTFB
FCP
LCP
FID
CLS
这是Next.js
提供的独有的指标,用来衡量 hydrate
和 render
时间
Next.js-hydration
:页面开始和完成hydrate
所需的时间(以毫秒为单位)Next.js-route-change-to-render
:页面在路由改变后到开始渲染的时间(以毫秒为单位)Next.js-render
: 路由更改后到页面完成渲染的时间(以毫秒为单位)经过这个函数咱们就能够建立本身的性能分析报告,这还不香么!
动态加载、动态路由匹配等 next10
均已经支持,须要的能够移步文档哦~ www.nextjs.cn/docs/gettin…
什么是 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(鼓励师划掉),路过的朋友给个三连叭~你的支持是萱酱创做的动力(说的我都感动了),我会持续更新~