一、SSR 核心概念
-
同构应用:
- 服务端渲染(Node.js)和客户端渲染(浏览器)共用同一套代码
- 服务端生成完整 HTML,客户端激活为交互应用
-
关键优势:
- 更好的 SEO(搜索引擎优化)
- 首屏加载速度提升
- 统一的代码逻辑
二、实现方式选择
-
官方推荐方案:
- Nuxt.js:基于 Vue 的高层框架,内置 SSR 支持(约定优于配置)
- Vue CLI + @vue/server-renderer:手动配置(适合定制化需求)
-
本示例采用手动配置方式:
- 理解 SSR 底层原理
- 便于根据项目需求灵活调整
三、基本实现步骤
1. 项目初始化
bash
npm init -y
npm install vue @vue/server-renderer express
2. 目录结构设计
plaintext
project/
├── src/
│ ├── app.js # Vue应用实例
│ ├── entry-client.js # 客户端入口
│ ├── entry-server.js # 服务端入口
│ └── components/ # 组件目录
├── server.js # 服务器主文件
├── webpack.client.config.js # 客户端打包配置
└── webpack.server.config.js # 服务端打包配置
3. 创建 Vue 应用
javascript
// src/app.js
import { createApp } from 'vue'
import App from './App.vue'
export function createVueApp() {
const app = createApp(App)
return { app }
}
4. 服务端入口
javascript
// src/entry-server.js
import { createVueApp } from './app'
export default (context) => {
return new Promise((resolve, reject) => {
const { app } = createVueApp()
// 这里可以进行路由匹配和数据预取等操作
resolve(app)
})
}
5. 客户端入口
javascript
// src/entry-client.js
import { createVueApp } from './app'
const { app } = createVueApp()
// 挂载应用到DOM
app.mount('#app')
6. 服务器配置
javascript
// server.js
const express = require('express')
const { createSSRApp } = require('vue')
const { renderToString } = require('@vue/server-renderer')
const server = express()
const fs = require('fs')
const path = require('path')
// 静态资源处理
server.use(express.static(path.resolve(__dirname, './dist/client')))
// 服务端渲染处理
server.get('*', async (req, res) => {
const template = fs.readFileSync(
path.resolve(__dirname, './dist/client/index.html'),
'utf-8'
)
// 创建Vue应用实例
const { createVueApp } = require('./dist/server/entry-server.js')
const { app } = createVueApp()
// 将Vue应用渲染为HTML字符串
const appHtml = await renderToString(app)
// 替换模板中的占位符
const html = template.replace('<!--app-html-->', appHtml)
// 返回渲染后的HTML
res.send(html)
})
server.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})
7. Webpack 配置
javascript
// webpack.client.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
target: 'web',
entry: './src/entry-client.js',
output: {
path: path.resolve(__dirname, './dist/client'),
filename: 'client-bundle.js'
},
module: {
rules: [
{ test: /.vue$/, use: 'vue-loader' },
{ test: /.js$/, use: 'babel-loader' }
]
},
plugins: [new VueLoaderPlugin()]
}
javascript
// webpack.server.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
target: 'node',
entry: './src/entry-server.js',
output: {
path: path.resolve(__dirname, './dist/server'),
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{ test: /.vue$/, use: 'vue-loader' },
{ test: /.js$/, use: 'babel-loader' }
]
},
plugins: [new VueLoaderPlugin()]
}
四、构建和运行
bash
# 安装依赖
npm install webpack webpack-cli vue-loader vue-template-compiler babel-loader @babel/core @babel/preset-env --save-dev
# 构建客户端和服务端包
npx webpack --config webpack.client.config.js
npx webpack --config webpack.server.config.js
# 启动服务器
node server.js
一、SSR 的主优缺点如下:
一、SSR 的主要优点
1. SEO 友好
- 原理:搜索引擎爬虫直接获取完整 HTML 内容,无需执行 JavaScript。
- 场景:电商产品页、新闻资讯、博客等需要搜索引擎收录的页面。
- 对比 CSR:传统 SPA(单页应用)的内容由 JS 动态生成,爬虫可能无法抓取有效信息。
2. 首屏加载速度更快
- 原理:用户首次请求时直接获得渲染好的 HTML,无需等待 JS 加载和执行。
- 数据:Google 研究表明,页面加载时间每增加 1 秒,转化率下降 20%。
- 关键指标:TTFB(Time To First Byte)显著缩短,尤其在弱网环境下优势明显。
3. 更好的用户体验
- 无白屏等待:直接展示完整内容,减少用户感知的加载时间。
- 适用场景:内容型网站(如知乎、微博)、电商详情页等。
4. 统一的代码逻辑
- 同构应用:服务端和客户端共用大部分代码(如路由、状态管理)。
- 维护成本:减少代码冗余,提升开发效率。
5. 性能优化潜力
- 组件级缓存:服务端缓存渲染结果,重复请求直接返回 HTML 片段。
- 按需加载:结合客户端 hydration,只加载当前页面所需资源。
二、SSR 的主要缺点
1. 开发复杂度高
- 环境差异:服务端没有浏览器 API(如
window、localStorage),需要条件判断。 - 生命周期差异:部分 Vue/React 生命周期钩子仅在客户端执行。
- 调试困难:服务端和客户端的错误堆栈可能不一致。
2. 服务器负载增加
- 性能开销:每个请求都需要动态渲染 HTML,CPU 密集型操作。
- 成本影响:需要更多服务器资源,尤其在高并发场景下。
3. 部署和维护复杂
- 环境依赖:需要 Node.js 运行环境,传统静态站点部署方案不适用。
- 监控困难:服务端渲染错误可能导致整个页面无法访问。
4. 首屏交互延迟
- hydration 过程:客户端接管 HTML 后需要重新绑定事件,在此期间交互可能无响应。
- 解决方案:使用
vueuse/core的useIntersectionObserver等工具优化。