Vue 3 + Nuxt 3 服务端渲染入门教程:从零到可部署
Vue 3 + Nuxt 3 服务端渲染入门教程
适用读者:熟悉 Vue 3 组合式 API,希望掌握 Nuxt 3 SSR 的前端开发者。
预计学习时长:50 分钟 · 含博客站点练习
学习目标
完成本教程后,你将能够:
- 理解 SSR、SSG、CSR 的区别及 Nuxt 3 的渲染模式
- 创建 Nuxt 3 项目并理解目录约定
- 使用
useFetch、useAsyncData进行服务端数据获取 - 配置 SEO 元信息、结构化数据与 sitemap
- 选择并实施合适的部署策略
前置知识
- Vue 3 组合式 API(ref、computed、watch)
- TypeScript 基础
- HTML/CSS 基础
- 了解 HTTP 请求与 JSON 数据格式
第一章:为什么需要 SSR?
1.1 三种渲染模式
| 模式 | 渲染位置 | SEO | 首屏速度 | 交互性 |
|---|---|---|---|---|
| CSR | 浏览器 | 差 | 慢(需下载 JS) | 好 |
| SSR | 服务器 | 好 | 快 | 好 |
| SSG | 构建时 | 好 | 最快 | 好 |
Nuxt 3 支持全部三种模式,可按页面灵活配置。
1.2 Nuxt 3 技术栈
- Vue 3 + Composition API
- Nitro 服务端引擎(基于 H3)
- Vite 构建工具
- UnJS 生态(ofetch、unstorage 等)
第二章:项目创建与结构
2.1 初始化
npx nuxi@latest init my-blog
cd my-blog
npm install
npm run dev
# 访问 http://localhost:3000
2.2 目录结构
my-blog/
app/
app.vue # 根组件
pages/ # 文件系统路由
index.vue # /
about.vue # /about
posts/
[slug].vue # /posts/:slug
components/ # 自动导入组件
composables/ # 自动导入组合式函数
layouts/ # 布局
default.vue
assets/ # 需构建的静态资源
server/
api/ # 服务端 API 路由
posts.get.ts # GET /api/posts
public/ # 静态文件(不经过构建)
nuxt.config.ts # Nuxt 配置
2.3 根组件
<!-- app/app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
第三章:路由与页面
3.1 首页
<!-- app/pages/index.vue -->
<script setup lang="ts">
const { data: posts, pending } = await useFetch('/api/posts', {
query: { limit: 10 },
});
useSeoMeta({
title: '我的博客',
description: '分享技术与生活',
ogTitle: '我的博客',
ogDescription: '分享技术与生活',
});
</script>
<template>
<div>
<h1>最新文章</h1>
<div v-if="pending">加载中...</div>
<ul v-else>
<li v-for="post in posts" :key="post.id">
<NuxtLink :to="`/posts/${post.slug}`">
{{ post.title }}
</NuxtLink>
<p>{{ post.summary }}</p>
</li>
</ul>
</div>
</template>
3.2 动态路由
<!-- app/pages/posts/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const slug = route.params.slug as string;
const { data: post, error } = await useFetch(`/api/posts/${slug}`);
if (error.value) {
throw createError({ statusCode: 404, message: '文章不存在' });
}
useSeoMeta({
title: () => post.value?.title,
description: () => post.value?.summary,
ogImage: () => post.value?.cover,
});
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.value?.title,
datePublished: post.value?.publishedAt,
}),
},
],
});
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<time>{{ post.publishedAt }}</time>
<div v-html="post.content" />
</article>
</template>
第四章:数据获取模式
4.1 useFetch vs useAsyncData
// useFetch:语法糖,自动处理 URL 和 key
const { data } = await useFetch('/api/posts');
// useAsyncData:更灵活,可包装任意异步函数
const { data } = await useAsyncData('posts', () =>
$fetch('/api/posts', { query: { tag: 'vue' } })
);
4.2 服务端 API(BFF 模式)
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const limit = Number(query.limit) || 10;
// 可在此调用外部 API 或数据库
const posts = await $fetch('https://cms.example.com/api/posts', {
query: { limit },
});
return posts;
});
4.3 避免双重获取
useFetch 在服务端执行后,会将数据序列化到 HTML 中,客户端 hydration 时不会重复请求。确保使用 await 在 <script setup> 顶层。
第五章:布局与组件
5.1 默认布局
<!-- app/layouts/default.vue -->
<template>
<div class="layout">
<header>
<nav>
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/about">关于</NuxtLink>
</nav>
</header>
<main>
<slot />
</main>
<footer>© 2026 My Blog</footer>
</div>
</template>
5.2 自动导入组件
<!-- app/components/PostCard.vue -->
<script setup lang="ts">
defineProps<{
title: string;
summary: string;
slug: string;
}>();
</script>
<template>
<article class="post-card">
<NuxtLink :to="`/posts/${slug}`">
<h3>{{ title }}</h3>
<p>{{ summary }}</p>
</NuxtLink>
</article>
</template>
在页面中直接使用 <PostCard />,无需 import。
第六章:配置与部署
6.1 nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
devtools: { enabled: true },
runtimeConfig: {
cmsApiKey: '', // 仅服务端
public: {
siteUrl: 'https://example.com',
},
},
app: {
head: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
link: [{ rel: 'icon', href: '/favicon.ico' }],
},
},
nitro: {
prerender: {
routes: ['/sitemap.xml'],
},
},
});
6.2 渲染模式选择
// 单页 SSG(构建时生成)
// app/pages/about.vue
defineRouteRules({ prerender: true });
// 单页 SPA(客户端渲染)
// app/pages/dashboard.vue
defineRouteRules({ ssr: false });
// ISR(增量静态再生,需部署平台支持)
defineRouteRules({ isr: 3600 }); // 每小时重新生成
6.3 构建与部署
npm run build
node .output/server/index.mjs
部署选项:
| 平台 | 方式 |
|---|---|
| Vercel/Netlify | 连接 Git 自动部署 |
| Docker | FROM node:20-alpine + .output |
| 静态托管 | nuxt generate 生成 .output/public |
练习 / 作业
- 创建 Nuxt 3 项目,实现首页 + 文章详情两个 SSR 页面。
- 编写
server/api路由,返回模拟文章数据。 - 为文章详情页配置完整 SEO(title、description、og:image、JSON-LD)。
- 添加 default 布局,包含导航栏和页脚。
- 进阶:接入真实 CMS API,实现 ISR 文章页,部署到 Vercel。
FAQ
Q:Nuxt 3 和 Nuxt 2 区别大吗?
A:很大。Nuxt 3 基于 Vue 3 + Nitro,性能更好,API 更简洁。不建议新项目用 Nuxt 2。
Q:useFetch 和 onMounted 里 fetch 有什么区别?
A:useFetch 在 SSR 时服务端执行,数据直接进入 HTML;onMounted 仅客户端执行,不利于 SEO 和首屏。
Q:如何处理认证页面?
A:敏感页面设置 ssr: false 或使用 middleware 在服务端检查 cookie/session。
Q:Nuxt 3 支持 Pinia 吗?
A:支持。安装 @pinia/nuxt 模块即可,SSR 时需注意状态序列化。
Q:如何调试 SSR 问题?
A:查看页面源代码(非 DevTools Elements),确认数据已渲染在 HTML 中。使用 console.log 在服务端会输出到终端。
小结
Nuxt 3 让 Vue 开发者轻松获得 SSR 能力。核心要点:理解目录约定、用 useFetch 在顶层获取数据、配置 useSeoMeta 优化 SEO、按页面选择合适的渲染模式。完成本教程的博客练习后,你已具备构建 SEO 友好全栈站点的能力。