Vue 3 + Nuxt 3 服务端渲染入门教程:从零到可部署

2026-06-12 14:22:54

Vue 3 + Nuxt 3 服务端渲染入门教程

适用读者:熟悉 Vue 3 组合式 API,希望掌握 Nuxt 3 SSR 的前端开发者。
预计学习时长:50 分钟 · 含博客站点练习

学习目标

完成本教程后,你将能够:

  1. 理解 SSR、SSG、CSR 的区别及 Nuxt 3 的渲染模式
  2. 创建 Nuxt 3 项目并理解目录约定
  3. 使用 useFetchuseAsyncData 进行服务端数据获取
  4. 配置 SEO 元信息、结构化数据与 sitemap
  5. 选择并实施合适的部署策略

前置知识

  • 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

练习 / 作业

  1. 创建 Nuxt 3 项目,实现首页 + 文章详情两个 SSR 页面。
  2. 编写 server/api 路由,返回模拟文章数据。
  3. 为文章详情页配置完整 SEO(title、description、og:image、JSON-LD)。
  4. 添加 default 布局,包含导航栏和页脚。
  5. 进阶:接入真实 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 友好全栈站点的能力。