前言
Vue.js 是一个构建客户端应用的框架,组件的代码会在浏览器中运行,然后向页面输出 DOM 元素,也就是我们最常用的方式,即 客户端渲染(client-side rendering,CSR).
实际上 Vue.js 还可以在 Node.js 环境中运行,即可将相同组件渲染成相应的字符串,并发送给浏览器进行渲染,这就是 服务端渲染(server-side rendering,SSR).
Vue.js 作为现代前端框架,除了能够分别支持 CSR 或 SSR 渲染之外,还能够同时支持 CSR 和 SSR,这就是所谓的 同构渲染(isomorphic rendering).
客户端渲染(CSR)
渲染流程
客户端渲染大致流程
对应的 performance 面板的快照
CSR 优点
通常 客户端渲染 伴随着 单页面应用(single-page application,SPA) 和 前端路由 等,相比于早期的 服务端路由 的渲染方式带来了一定的优势:
- 用户体验更好
- 早期的 服务端路由 方式,会导致从 A 页面跳转到 B 页面时,页面会重新刷新并对整个页面重新进行渲染,这个过程会让用户感觉不够流畅,基于 前端路由 的方式并不会真正进行 页面跳转,带来了更高的流畅度
- 占用服务端资源少
- 早期的 服务端路由 方式,会将完整的页面返回给客户端,意味着要在 服务端 访问数据库,并且需要将对应的数据和页面进行融合,所以对服务端而言,一次路由访问就需要做这两件事,若访问的并发量高,会导致服务端需要额外处理这些计算,自然会占用服务端有限的资源
- CSR 渲染则是交由客户端进行处理,服务端不需要关心渲染计算的过程,减轻了服务端的压力
CSR 缺陷
客户端渲染 仍是目前使用最多得渲染模式,除非一些特殊场景下 CSR 无法满足对应的需求:
- "白屏" 时间较长
- 主要是因为 CSR 渲染需要
*.js
的支持,而*.js
又必须保证*.html
被接收和解析,*.html
又强依赖于当前的 网络环境,因此,在差网环境下回导致 白屏时间过长,特别是在移动网络环境下
- 主要是因为 CSR 渲染需要
- 对 SEO 的支持不友好
- 这一点也很好理解,因为 白屏时间较长 导致在一段时间内没有重要的内容能够交由 搜索引擎 进行分析、分类、打标签等,并且 搜索引擎 并不会等待页面渲染完成,因此对 SEO 优化并不友好
服务端渲染(SSR)
渲染流程
简单的渲染流程
搭建 node 服务
搭建一个简单的 node 服务来观察 SSR 的效果,内容比较简单不过多赘述,其中需要注意的是:
- Node.js 服务器是长期运行的进程,当代码第一次被导入进程时,它会被执行一次然后 保留在内存里
- 如果只创建了一个 vue 的单例对象,它将被 每次发来的请求共享,这是不符合实际需求的,因此,需要为每个请求重新生成一个 vue 实例,避免相互影响
效果演示
以下是 node 环境相关代码:
const express = require("express");
const { createSSRApp } = require("vue");
const { renderToString } = require("@vue/server-renderer");
const app = express();
// Node.js 服务器是长期运行的进程,当代码第一次被导入进程时,它会被执行一次然后保留在内存里
// 如果只创建了一个 vue 的单例对象,它将被每次发来的请求共享,这是不符合实际需求的
// 因此,需要为每个请求,重新生成一个 vue 实例,避免相互影响
function createApp(msg) {
return createSSRApp({
data() {
return {
msg,
};
},
template: `<h1>{{ msg }}</h1>`,
});
}
function getHtmlStrWrap(contentStr) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<h3><a href="/home"> home </a></h3>
<h3><a href="/about"> about </a></h3>
<h3><a href="/test"> error path </a></h3>
<div id="app">${contentStr}</div>
</html>
`;
}
app.get("/home", async (req, res, next) => {
const vueStr = await renderToString(createApp("Home Page!!"));
const htmlStr = getHtmlStrWrap(vueStr);
res.end(htmlStr);
});
app.get("/about", async (req, res, next) => {
const vueStr = await renderToString(createApp("About Page!!"));
const htmlStr = getHtmlStrWrap(vueStr);
res.end(htmlStr);
});
app.get("*", async (req, res, next) => {
const vueStr = await renderToString(createApp("Not Found Page!!"));
const htmlStr = getHtmlStrWrap(vueStr);
res.end(htmlStr);
});
app.listen(8000, (err) => {
if (err) {
console.error("server fail:", err);
return;
}
console.log("server is runing at http://localhost:8000");
});
SSR 优势
- 不存在 白屏时间过长 问题
- 更快的内容呈现,尤其是网络连接缓慢或设备运行速度缓慢的时候,服务端标记 不需要等待所有的 JavaScript 都被下载并执行之后才显示,所以用户可以更快看到完整的渲染好的内容
- 更好的搜索引擎优化 (SEO)
- 搜索引擎爬虫会直接读取 完整渲染 出来的页面
- 通过 API 调用获取的内容,爬虫是不会等待页面加载完成
SSR 缺点
- 需要保证开发一致性
- 浏览器特有的代码只能在特定的生命周期钩子中使用
- 一些外部库在服务端渲染应用中可能需要经过特殊处理
- 需要更多的构建设定和部署要求
- 不同于一个完全静态的 SPA 可以部署在任意的静态文件服务器,服务端渲染应用需要一个能够运行 Node.js 服务器的环境
- 更多的服务端负载
- 在 Node.js 中渲染一个完整的应用,会比仅供应静态文件产生更密集的 CPU 运算
- 若访问流量很高,就必须要准备好与其负载相对应的服务器,以及采取 合理的缓存策略
SSR 和 预渲染
基于以上 SSR 的优缺点对比,只有明确具体页面的具体需求才能更好的决定是否需要使用 SSR,如果只是希望通过 SSR 来改善一些 推广页面 (如 /
、/about
、/contact
等) 的 SEO,那么应该优先考虑 预渲染 的方式.
SSR 是一个 动态编译 HTML 的 web 服务器,而 预渲染 可以在 构建时 为指定的路由 生成静态 HTML 文件,且预渲染的设置比 SSR 更加简单,也支持生成为一个完全静态的 HTML 文件.
预渲染 需要和 打包构建工具(webpack、rollup 等) 进行配合,如 webpack
,就可通过 prerender-spa-plugin
来支持 预渲染.
同构渲染(isomorphic rendering)
基于 CSR 和 SSR 各自的优缺点,如果可以将它们进行结合,那么就可以实现互补,而这也就是 同构渲染 需要做的事,其中的 同构 就是指 应用代码的主体 可以同时运行在 服务端 和 客户端.
大致流程如下:
- 在服务端,Vue 组件会被渲染为静态的 HTML 字符串,然后发送给客户端浏览器
- 在浏览器端,需要渲染这段 HTML 内容,即此时页面中已经存在 对应的 DOM 元素,除此之外该组件还会被打包到一个 JavaScript 文件中,并在客户端被下载和解释执行,也就是进入 客户端激活,后续页面内容的渲染都不需要服务器进行处理动态编译处理.
构建同构渲染
服务端 要渲染 Vue 组件 意味着需要处理 *.vue
、*.css
、*.ts
等依赖模块,而这些是 node 本身就不能处理的内容,也不是 renderToString
能够处理的,因此需要借助 打包构建工具(如 webpack) 进行处理.
客户端 实际也需要一个独立的客户端构建版本,虽然最新版本的 Node.js 完全支持 ES2015 特性,但对于旧的浏览器仍然需要对代码进行转译、兼容处理.
基本思路,使用 webpack 同时打包客户端和服务端应用,其中服务端的包会被引入到服务端用来渲染 HTML,同时客户端的包会被送到浏览器用于 激活静态标记.
与之对应的两个入口文件就是:entry-client.js
和 entry-server.js
篇幅有限,更多具体的配置可参见 官方文档
效果演示
以下是根据官方文档配置得到运行效果:
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿