Vue.js 服务端渲染(SSR)
什么是 SSR?
服务端渲染(Server-Side Rendering,SSR)是指在服务器上将 Vue 组件渲染成 HTML 字符串,然后发送给客户端。这与客户端渲染(CSR)不同,后者在浏览器中渲染组件。
SSR 的优势
1. 更好的 SEO
搜索引擎爬虫可以直接看到完整的页面内容。
2. 更快的首屏加载
用户可以更快地看到页面内容,无需等待 JavaScript 下载和执行。
3. 更好的性能(在某些情况下)
对于内容密集型应用,SSR 可以提供更好的性能。
SSR 的劣势
- 服务器负载更高
- 开发复杂度增加
- 部署和维护成本更高
- 某些浏览器 API 不可用
使用 Nuxt.js(推荐)
Nuxt.js 是基于 Vue 的 SSR 框架,提供了开箱即用的 SSR 支持。
安装 Nuxt
bash
npx nuxi@latest init my-app
cd my-app
npm install
npm run devNuxt 项目结构
my-app/
├── pages/ # 页面(自动路由)
├── components/ # 组件
├── layouts/ # 布局
├── composables/ # 组合式函数
├── plugins/ # 插件
├── middleware/ # 中间件
├── public/ # 静态资源
├── server/ # 服务端代码
└── nuxt.config.ts # 配置文件创建页面
vue
<!-- pages/index.vue -->
<script setup>
const title = 'Welcome to Nuxt!'
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>This page is server-rendered!</p>
</div>
</template>数据获取
vue
<!-- pages/posts/[id].vue -->
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.id}`)
</script>
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
</template>手动实现 SSR
如果不使用 Nuxt,可以手动实现 SSR:
1. 安装依赖
bash
npm install express vue @vue/server-renderer2. 创建服务器
javascript
// server.js
import express from 'express'
import { createSSRApp } from 'vue'
import { renderToString } from '@vue/server-renderer'
const server = express()
server.get('*', async (req, res) => {
const app = createSSRApp({
data() {
return {
message: 'Hello from SSR!'
}
},
template: `<div>{{ message }}</div>`
})
const html = await renderToString(app)
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR</title>
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`)
})
server.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})3. 客户端激活(Hydration)
javascript
// client.js
import { createSSRApp } from 'vue'
const app = createSSRApp({
data() {
return {
message: 'Hello from SSR!'
}
},
template: `<div>{{ message }}</div>`
})
app.mount('#app')SSR 数据预取
vue
<!-- pages/users.vue -->
<script setup>
// 服务端和客户端都会执行
const { data: users, pending, error } = await useFetch('/api/users')
</script>
<template>
<div>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else>
<h1>Users</h1>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</div>
</template>SSR 生命周期
在 SSR 中,只有 beforeCreate 和 created 钩子会在服务端执行:
vue
<script setup>
import { onMounted, onServerPrefetch } from 'vue'
// 只在服务端执行
onServerPrefetch(async () => {
console.log('Server prefetch')
// 预取数据
})
// 只在客户端执行
onMounted(() => {
console.log('Client mounted')
// 客户端逻辑
})
</script>处理浏览器 API
某些浏览器 API 在服务端不可用:
vue
<script setup>
import { ref, onMounted } from 'vue'
const windowWidth = ref(0)
// ❌ 错误:window 在服务端不存在
// const windowWidth = ref(window.innerWidth)
// ✅ 正确:在客户端钩子中使用
onMounted(() => {
windowWidth.value = window.innerWidth
})
// 或者使用条件判断
if (typeof window !== 'undefined') {
windowWidth.value = window.innerWidth
}
</script>状态管理与 SSR
Pinia with SSR
javascript
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
user: null
}),
actions: {
async fetchUser() {
// 服务端和客户端都会执行
const response = await fetch('/api/user')
this.user = await response.json()
}
}
})vue
<!-- pages/profile.vue -->
<script setup>
const userStore = useUserStore()
// 服务端预取数据
await userStore.fetchUser()
</script>
<template>
<div>
<h1>{{ userStore.user?.name }}</h1>
</div>
</template>缓存策略
页面级缓存
javascript
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // 预渲染
'/api/**': { cache: { maxAge: 60 } }, // API 缓存
'/admin/**': { ssr: false } // 禁用 SSR
}
})组件级缓存
vue
<script setup>
// 缓存组件 60 秒
defineOptions({
cache: {
maxAge: 60
}
})
</script>静态站点生成(SSG)
Nuxt 支持静态站点生成:
bash
# 生成静态站点
npm run generatejavascript
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
target: 'static'
})混合渲染
可以为不同路由选择不同的渲染模式:
javascript
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // SSG
'/blog/**': { swr: 3600 }, // ISR (增量静态再生)
'/admin/**': { ssr: false }, // CSR
'/api/**': { cors: true } // API
}
})SEO 优化
vue
<!-- pages/blog/[id].vue -->
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.id}`)
// 设置 meta 标签
useHead({
title: post.value.title,
meta: [
{ name: 'description', content: post.value.excerpt },
{ property: 'og:title', content: post.value.title },
{ property: 'og:description', content: post.value.excerpt },
{ property: 'og:image', content: post.value.image }
]
})
</script>
<template>
<article>
<h1>{{ post.title }}</h1>
<div v-html="post.content"></div>
</article>
</template>性能优化
1. 代码分割
vue
<script setup>
// 懒加载组件
const HeavyComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>2. 预取关键资源
vue
<script setup>
useHead({
link: [
{ rel: 'preload', href: '/fonts/main.woff2', as: 'font' },
{ rel: 'prefetch', href: '/api/data' }
]
})
</script>3. 使用 CDN
javascript
// nuxt.config.ts
export default defineNuxtConfig({
app: {
cdnURL: 'https://cdn.example.com'
}
})部署
Vercel
bash
# 安装 Vercel CLI
npm i -g vercel
# 部署
vercelNetlify
bash
# 构建命令
npm run build
# 输出目录
.output/publicNode.js 服务器
bash
# 构建
npm run build
# 启动
node .output/server/index.mjs总结
- SSR 提供更好的 SEO 和首屏性能
- Nuxt.js 是 Vue SSR 的最佳选择
- 注意服务端和客户端的差异
- 合理使用缓存策略
- 可以混合使用 SSR、SSG 和 CSR
- 针对不同场景选择合适的渲染模式
下一步
接下来学习 工具链和生态,了解 Vue 生态系统中的工具和库。