vue3服务端渲染SSR是如何工作的
Vue 3 的服务端渲染(SSR)通过以下步骤和机制实现:
1. 基本概念
服务端渲染(SSR) 是指在服务器端生成完整的 HTML 页面,再将其发送到客户端。与客户端渲染(CSR)相比,SSR 的优势在于:
-
更好的 SEO:搜索引擎可以直接抓取服务端生成的 HTML。
-
更快的首屏加载:用户无需等待 JavaScript 下载和执行即可看到内容。
-
统一的心智模型:你可以使用相同的语言以及相同的声明式、面向组件的心智模型来开发整个应用,而不需要在后端模板系统和前端框架之间来回切换。
2. Vue 3 SSR 的核心流程
Vue 3 的 SSR 流程分为以下步骤:
2.1 服务器端渲染
-
创建 Vue 应用实例:在服务器端初始化 Vue 应用。
-
处理路由:根据请求的 URL 匹配路由,并预取数据。
-
渲染 HTML:将 Vue 组件渲染为 HTML 字符串。
-
注入状态:将服务器端获取的数据(如 Pinia 状态)序列化到 HTML 中。
-
返回页面:将完整的 HTML 发送到客户端。
2.2 客户端激活(Hydration)
-
加载客户端代码:浏览器下载 JavaScript 文件。
-
复用 HTML:Vue 客户端代码会“激活”服务端渲染的 HTML,使其变为动态应用。
-
接管交互:客户端 Vue 应用开始运行,处理后续的用户交互。
3. 实现步骤
3.1 项目结构
典型的 Vue 3 SSR 项目结构如下:
project/
├── src/
│ ├── entry-client.js # 客户端入口
│ ├── entry-server.js # 服务器端入口
│ ├── app.js # 通用的 Vue 应用创建逻辑
│ ├── router.js # 路由配置
│ └── stores/ # 状态管理(如 Pinia)
├── server.js # 服务器代码(如 Express)
└── index.template.html # HTML 模板
3.2 服务器端入口文件(entry-server.js)
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createPinia } from 'pinia'
export default function (url) {
const app = createSSRApp(App)
const router = createRouter()
const pinia = createPinia()
app.use(router).use(pinia)
router.push(url)
await router.isReady()
return { app, router, pinia }
}
3.3 客户端入口文件(entry-client.js)
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createPinia } from 'pinia'
const app = createSSRApp(App)
const router = createRouter()
const pinia = createPinia()
app.use(router).use(pinia)
// 等待路由准备就绪后挂载应用
router.isReady().then(() => {
app.mount('#app')
})
3.4 服务器代码(server.js)
使用 Express 作为服务器:
import express from 'express'
import { renderToString } from '@vue/server-renderer'
import { createPinia } from 'pinia'
import createApp from './src/entry-server.js'
const server = express()
server.get('*', async (req, res) => {
const { app, router, pinia } = createApp(req.url)
// 预取数据(例如 Pinia store 的异步操作)
await Promise.all(router.currentRoute.value.matched.map(route => {
if (route.components && route.components.default.asyncData) {
return route.components.default.asyncData({ pinia })
}
}))
const appHtml = await renderToString(app)
const piniaState = JSON.stringify(pinia.state.value)
// 将 HTML 和状态注入模板
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Vue 3 SSR</title>
</head>
<body>
<div id="app">${appHtml}</div>
<script>window.__PINIA_STATE__ = ${piniaState}</script>
<script src="/client-bundle.js"></script>
</body>
</html>
`
res.send(html)
})
server.listen(3000)
3.5 客户端激活(Hydration)
在客户端入口中初始化 Pinia 并同步服务端状态:
import { createPinia } from 'pinia'
const pinia = createPinia()
if (window.__PINIA_STATE__) {
pinia.state.value = window.__PINIA_STATE__
}
4. 关键技术点
4.1 数据预取(Data Prefetching)
- 路由组件静态方法:在路由组件中定义
asyncData方法,用于服务端预取数据。
export default {
asyncData({ pinia }) {
return pinia.useStore().fetchData()
}
}
4.2 避免状态污染
- 每个请求独立实例:为每个请求创建独立的 Vue 应用、路由和 Pinia 实例,避免状态共享问题。
4.3 客户端激活(Hydration)
- 复用 HTML 结构:客户端 Vue 应用会检查服务端渲染的 DOM 结构,并绑定事件监听器,而不是重新渲染。
5. 性能优化
5.1 流式渲染(Streaming)
Vue 3 支持流式渲染,逐步发送 HTML 到客户端,提升首屏加载速度:
import { renderToNodeStream } from '@vue/server-renderer'
const stream = renderToNodeStream(app)
stream.pipe(res)
5.2 组件级缓存
通过 serverCacheKey 缓存高频组件的渲染结果:
export default {
name: 'HeavyComponent',
serverCacheKey: props => props.id,
}
6. 工具链
-
Vite SSR 支持:Vite 提供开箱即用的 SSR 开发体验。
-
Nuxt 3:基于 Vue 3 的 SSR 框架,简化配置和开发流程。
总结
Vue 3 的 SSR 实现通过以下步骤完成:
-
服务端初始化:创建独立的应用实例、路由和状态管理。
-
数据预取:在渲染前获取组件所需数据。
-
HTML 渲染:将 Vue 组件渲染为 HTML 字符串。
-
状态注入:将服务端状态序列化到客户端。
-
客户端激活:复用服务端 HTML,使其变为动态应用。
通过合理配置和优化,Vue 3 的 SSR 能够显著提升 SEO 和首屏性能,适用于需要快速加载和搜索引擎友好的应用场景。
更多vue相关插件及后台管理模板可访问vue admin reference,代码详情请访问github