CSR 优缺点
优点
- 整个网站打包进 JavaScript 里,当 JavaScript 下载完毕后,相当于网站的页面资源都被下载好了。这样在跳转新页面的时候,不需要向服务器再次请求资源( JavaScript 会直接操作 DOM 进行页面渲染),从而让整个网站的使用体验上更加流畅
缺点
- 在 JavaScript 体积较大的情况下,会有白屏问题
- 因为会先下载一个空的 HTML,然后才通过 JavaScript 进行渲染,这个空的 HTML 会导致某些搜索引擎无法通过爬虫正确获取网站信息,从而影响网站的搜索引擎排名
SSR
优点
- HTML 在服务器端就已经渲染好了,浏览器拿到就可以渲染,减少白屏时间
- 服务器端渲染拥有良好的首屏性能和 SEO
缺点
- 每次跳转页面都要向服务器重新请求,意味着用户每次切换页面都要等待一小段时间
- SSR 相比 CSR 会占用较多的服务器端资源
// 以 vue 为例
import { renderToString } from 'vue/server-renderer'
import { createSSRApp } from 'vue'
// 一个计数的 vue 组件
function createApp() {
// 通过 createSSRApp 创建一个vue实例
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`,
});
}
const app = createApp();
// 通过 renderToString 将 vue 实例渲染成字符串
renderToString(app).then((html) => {
// 将字符串插入到 html 模板中
const htmlStr = `
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Example</title>
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`;
console.log(htmlStr);
});
Server + Client 同构
- 开始的步骤和 SSR 相同,将生成的 HTML 字符串返回给客户端,同时将 CSR 需要的 JavaScript 也一并发送给客户端
- 客户端在接收到 SSR 生成的 HTML 后,页面还会再执行一次 CSR 的流程
- 客户端只有请求的第一个页面是在服务器端渲染的,其它页面则都是在客户端进行的
- 这样就同时兼顾首屏、SEO和用户体验的网站
Hydrate 水合(客户端激活)
- 服务器执行应用的初始渲染,生成静态 HTML,并将其发送给客户端,这一步其实发送的是静态的模版( Dehydrate 脱水)
- 客户端加载额外的 JavaScript 代码,并在已有的静态 HTML 上绑定事件监听器等,使页面变得可交互
- SSR 的瓶颈也就取决于 Hydrate 的过程
数据的获取和初始化
- 挂载到 window 上或者 Vuex、Pinia 等其它方案
const htmlStr = `
<!DOCTYPE html>
<html>
<head>
...
// 将数据格式化成json字符串,放到script标签中
<script>window.__INITIAL_DATA__ = ${JSON.stringify(initData)}</script>
</head>
...
</html>
`;
组件中获取数据
function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`,
// 自定义一个名为 asyncData 的函数
asyncData: async () => {
// 在处理远程数据并 return 出去
const data = await getSomeData()
return data;
},
async mounted() {
// 如果已经有数据了,直接从 window 中获取
if (window.__INITIAL_DATA__) {
// 有服务端数据时,使用服务端渲染时的数据
this.count = window.__INITIAL_DATA__;
window.__INITIAL_DATA__ = undefined;
return;
} else {
// 如果没有数据,就请求数据
this.count = await getSomeData();
}
}
});
}
预加载资源
- 在打包过程中生成 manifest
- 作用是将打包后的模块 ID 与它们关联的 Chunk 和资源文件进行映射
- 依靠这个 manifest 获取资源的路径,然后创建 Link 标签拼接到 HTML 模板中即可
避免应用单例
- 服务器端返回给客户端的每个请求都应该是全新的、独立的应用程序实例,避免直接将对象或变量创建在全局作用域,否则它将在所有请求之间共享,在不同请求之间造成状态污染
避免全局副作用代码
- 比如 vue 服务器端渲染只会执行 beforeCreate 和 created 生命周期,应该避免在这两个生命周期里产生全局副作用的代码
- 例如使用 setInterval 设置定时器。在纯客户端的代码中,我们可以设置一个定时器,然后在 beforeDestroy 或 destroyed 生命周期中将其销毁。但是在 SSR 期间并不会调用销毁钩子函数,所以定时器将永远保留下来,最终造成服务器内存溢出