vue
Vue 3 SSR 与 Nuxt 3
By AI-Writer 18 min read
Vue 3 SSR 与 Nuxt 3
服务端渲染(SSR)是现代前端架构的重要话题,它可以提升首屏加载性能、改善 SEO、并提供更好的用户体验。Nuxt 3 是 Vue 生态中最成熟的 SSR 框架,本文详细介绍其核心概念和使用方法。
为什么需要 SSR
SPA 的问题
传统 SPA(单页面应用)存在以下问题:
- 首屏白屏:浏览器需要下载并执行 JS 后才能渲染内容
- SEO 不友好:搜索引擎爬虫可能无法正确索引动态内容
- 首屏加载慢:所有资源打包在一个大文件中
SSR 的优势
- 更快的内容呈现:服务器返回完整的 HTML,用户立即看到内容
- 更好的 SEO:搜索引擎可以直接抓取 HTML 内容
- 更好的性能感知:减少首屏等待时间
SSR 的挑战
- 更高的服务器负载:每个请求都需要服务端渲染
- 复杂的状态管理:需要区分服务端和客户端的数据
- 更多的调试复杂度:需要在服务端和客户端都进行调试
Vue 3 SSR 基础
使用 Vite SSR
bash
npm install vite-ssrjavascript
// server.js
import express from 'express'
import { renderToString } from 'vue/server'
import { createSSRApp } from 'vue'
import App from './src/App.vue'
const server = express()
server.get('*', async (req, res) => {
const app = createSSRApp(App)
// 服务端数据预取
app.provide('serverData', { url: req.url })
const html = await renderToString(app)
res.send(`
<!DOCTYPE html>
<html>
<head><title>SSR App</title></head>
<body>
<div id="app">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`)
})
server.listen(3000)服务端与客户端差异
vue
<script setup>
// 在 setup 中访问 window 会导致 SSR 失败
// 错误:
// const width = window.innerWidth
// 正确:使用 import.meta.env.SSR 判断
import { ref, onMounted, onServerPrefetch } from 'vue'
const data = ref(null)
// 服务端预取数据
onServerPrefetch(async () => {
// 仅在服务端执行
const response = await fetch('https://api.example.com/data')
data.value = await response.json()
})
// 客户端挂载后的操作
onMounted(() => {
// 仅在客户端执行
console.log('Client mounted')
})
// 通用逻辑(服务端和客户端都执行)
const config = { apiUrl: '/api' }
</script>useFetch 组合函数
vue
<script setup>
import { ref } from 'vue'
// Nuxt 3 的 useFetch 自动处理 SSR/CSR 差异
const { data: posts, pending, error } = await useFetch('/api/posts', {
// 服务端预取
server: true,
// 懒加载(客户端执行)
lazy: false,
// 缓存键
key: 'posts',
// 转换响应
transform: (response) => response.data
})
</script>Nuxt 3 入门
Nuxt 3 是基于 Vue 3 的全栈框架,内置 SSR、路由、状态管理、自动导入等功能。
创建项目
bash
npx nuxi@latest init nuxt-app
cd nuxt-app
npm install
npm run dev目录结构
plaintext
nuxt-app/
├── pages/ # 页面目录(自动生成路由)
│ ├── index.vue # 首页 -> /
│ ├── about.vue # 关于页 -> /about
│ └── blog/
│ ├── index.vue # 博客列表 -> /blog
│ └── [id].vue # 博客详情 -> /blog/:id
├── components/ # 组件(自动导入)
├── layouts/ # 布局
├── composables/ # 组合式函数(自动导入)
├── server/ # 服务端 API
│ ├── api/
│ │ └── posts.get.ts # GET /api/posts
│ └── routes/
├── nuxt.config.ts # 配置文件
└── app.vue # 应用入口基础页面
vue
<!-- pages/index.vue -->
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<nav>
<NuxtLink to="/about">关于</NuxtLink>
<NuxtLink to="/blog">博客</NuxtLink>
</nav>
<Counter />
<slot />
</div>
</template>
<script setup>
// 页面 meta
useHead({
title: '首页',
meta: [
{ name: 'description', content: 'Nuxt 3 博客' }
]
})
const title = ref('欢迎来到我的博客')
const description = '使用 Nuxt 3 构建的现代化博客'
</script>自动导入机制
Nuxt 3 最大的特点之一是自动导入,显著减少样板代码:
自动导入的内容
ref,reactive,computed等 Vue 组合式 APIuseHead,useSeoMeta等 Nuxt 组合式函数components/目录下的所有组件composables/目录下的所有函数utils/目录下的工具函数
组件自动导入
vue
<!-- components/Header.vue -->
<template>
<header class="site-header">
<slot />
</header>
</template>vue
<!-- pages/index.vue -->
<!-- 无需 import,Header 会被自动引入 -->
<template>
<Header>Logo</Header>
</template>Composables 自动导入
javascript
// composables/useCounter.js
export function useCounter(initial = 0) {
const count = useState('count', () => initial)
function increment() {
count.value++
}
return { count, increment }
}vue
<!-- pages/index.vue -->
<!-- 无需 import -->
<script setup>
const { count, increment } = useCounter()
</script>数据获取
useFetch
vue
<script setup>
// 基本用法
const { data, pending, error, refresh } = await useFetch('/api/posts')
// 带参数
const { data: user } = await useFetch(`/api/users/${id}`, {
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
})
// 转换数据
const { data: formattedPosts } = await useFetch('/api/posts', {
transform: (posts) => posts.map(p => ({
...p,
formattedDate: new Date(p.date).toLocaleDateString()
}))
})
// 刷新数据
async function refreshData() {
await refresh()
}
</script>useAsyncData
vue
<script setup>
// 更精细的控制
const { data, pending, error, execute } = await useAsyncData(
'user-profile', // 缓存键
() => $fetch('/api/user'),
{
server: true, // 服务端执行
lazy: false, // 等待数据
default: () => null, // 默认值
transform: (data) => {
// 数据转换
return data
}
}
)
</script>服务端 API 路由
typescript
// server/api/posts/index.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = parseInt(query.page as string) || 1
const limit = parseInt(query.limit as string) || 10
// 模拟数据库查询
const posts = await db.posts.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' }
})
const total = await db.posts.count()
return {
posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
}
})typescript
// server/api/posts/[id].get.ts
export default defineEventHandler(async (event) => {
const id = parseInt(getRouterParam(event, 'id'))
const post = await db.posts.findUnique({
where: { id }
})
if (!post) {
throw createError({
statusCode: 404,
message: 'Post not found'
})
}
return post
})混合渲染模式
Nuxt 3 支持在不同路由使用不同的渲染策略:
渲染模式类型
- SSR:每个请求在服务端渲染
- SSG:构建时预渲染静态 HTML
- CSR:纯客户端渲染
- ISR:增量静态再生成
路由规则配置
typescript
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// 首页:SSR(实时数据)
'/': { ssr: true },
// 博客列表:ISR(每小时重新生成)
'/blog': { ssr: true, prerender: false, cache: { maxAge: 3600 } },
// 博客详情:SSG(预渲染所有页面)
'/blog/**': { prerender: true },
// 管理后台:CSR(客户端渲染)
'/admin/**': { ssr: false },
// API 路由:配置缓存
'/api/posts': { cache: { maxAge: 60, staleMaxAge: 600 } }
}
})动态渲染模式
vue
<script setup>
// 运行时切换渲染模式
definePageMeta({
ssr: true // 或 false
})
// 或者在 setup 中控制
if (process.server) {
// 服务端代码
}
</script>状态管理
useState
typescript
// composables/useAuth.ts
export const useAuth = () => {
const user = useState('user', () => null)
const login = async (credentials) => {
const response = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials
})
user.value = response.user
}
const logout = async () => {
await $fetch('/api/auth/logout', { method: 'POST' })
user.value = null
}
return {
user: readonly(user),
isAuthenticated: computed(() => !!user.value),
login,
logout
}
}Pinia 集成
bash
npm install @pinia/nuxttypescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})typescript
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})SEO 与 Meta
useHead
vue
<script setup>
useHead({
title: '博客文章',
meta: [
{ name: 'description', content: '文章描述' },
{ property: 'og:title', content: '社交分享标题' },
{ property: 'og:description', content: '社交分享描述' }
],
link: [
{ rel: 'canonical', href: 'https://example.com/current-page' }
]
})
</script>useSeoMeta
vue
<script setup>
useSeoMeta({
title: '博客文章',
ogTitle: '社交分享标题',
description: '文章描述',
ogDescription: '社交分享描述',
ogImage: '/images/og-image.jpg',
twitterCard: 'summary_large_image'
})
</script>动态 SEO
vue
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.id}`)
// 使用 post 数据设置 SEO
useSeoMeta({
title: () => `${post.value?.title} - 我的博客`,
description: () => post.value?.description
})
</script>部署
Node.js 服务器
bash
# 构建
npm run build
# 启动
node .output/server/index.mjsDockerfile
dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]Vercel / Netlify
typescript
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'vercel'
}
})性能优化
组件懒加载
vue
<script setup>
// 自动按需加载
const LazyHeavyChart = defineAsyncComponent(() =>
import('./HeavyChart.vue')
)
</script>
<template>
<LazyHeavyChart v-if="showChart" />
</template>图片优化
vue
<template>
<!-- Nuxt Image 自动优化 -->
<NuxtImg
src="/images/photo.jpg"
width="800"
height="600"
format="webp"
loading="lazy"
alt="Description"
/>
</template>总结
Nuxt 3 为 Vue 3 开发者提供了完整的 SSR 解决方案:
- 框架优势:开箱即用、自动导入、文件系统路由
- 渲染模式:SSR / SSG / CSR / ISR 按需选择
- 数据获取:
useFetch、useAsyncData自动处理服务端和客户端 - SEO 支持:
useHead、useSeoMeta轻松管理 meta 信息 - API 路由:在
server/api/目录创建服务端 API - 状态管理:
useState和 Pinia 双重支持 - 部署灵活:支持 Node.js、Docker、Vercel 等多种部署方式
掌握 Nuxt 3,你就能构建出性能优秀、SEO 友好、功能完整的 Vue 3 全栈应用。
#vue
#nuxt
#ssr
#server-side-rendering
#全栈
评论
A
Written by
AI-Writer
Related Articles
vue
#1 Vue 实例与模板语法
深入讲解 Vue 3 的应用创建方式(createApp)、模板语法核心规则(插值、指令、双向绑定),以及响应式数据(ref、reactive)和计算属性的使用
Read More