vue3服务端渲染SSR是如何工作的

142 阅读4分钟

vue3服务端渲染SSR是如何工作的

Vue 3 的服务端渲染(SSR)通过以下步骤和机制实现:

1. 基本概念

服务端渲染(SSR) 是指在服务器端生成完整的 HTML 页面,再将其发送到客户端。与客户端渲染(CSR)相比,SSR 的优势在于:

  • 更好的 SEO:搜索引擎可以直接抓取服务端生成的 HTML。

  • 更快的首屏加载:用户无需等待 JavaScript 下载和执行即可看到内容。

  • 统一的心智模型:你可以使用相同的语言以及相同的声明式、面向组件的心智模型来开发整个应用,而不需要在后端模板系统和前端框架之间来回切换。

2. Vue 3 SSR 的核心流程

Vue 3 的 SSR 流程分为以下步骤:

2.1 服务器端渲染

  1. 创建 Vue 应用实例:在服务器端初始化 Vue 应用。

  2. 处理路由:根据请求的 URL 匹配路由,并预取数据。

  3. 渲染 HTML:将 Vue 组件渲染为 HTML 字符串。

  4. 注入状态:将服务器端获取的数据(如 Pinia 状态)序列化到 HTML 中。

  5. 返回页面:将完整的 HTML 发送到客户端。

2.2 客户端激活(Hydration)

  1. 加载客户端代码:浏览器下载 JavaScript 文件。

  2. 复用 HTML:Vue 客户端代码会“激活”服务端渲染的 HTML,使其变为动态应用。

  3. 接管交互:客户端 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 实现通过以下步骤完成:

  1. 服务端初始化:创建独立的应用实例、路由和状态管理。

  2. 数据预取:在渲染前获取组件所需数据。

  3. HTML 渲染:将 Vue 组件渲染为 HTML 字符串。

  4. 状态注入:将服务端状态序列化到客户端。

  5. 客户端激活:复用服务端 HTML,使其变为动态应用。

通过合理配置和优化,Vue 3 的 SSR 能够显著提升 SEO 和首屏性能,适用于需要快速加载和搜索引擎友好的应用场景。

更多vue相关插件及后台管理模板可访问vue admin reference,代码详情请访问github