Vue 3 与 Nuxt 3 全栈框架

概述

Nuxt 3 是基于 Vue 3 的全栈框架,提供了服务器端渲染 (SSR)、静态站点生成 (SSG)、客户端渲染 (CSR) 等多种渲染模式,以及路由、状态管理、API 集成等开箱即用的功能。Nuxt 3 设计简洁、性能优异,是构建现代化 Web 应用的理想选择。本集将深入探讨 Nuxt 3 的核心特性和使用方法,帮助你构建全栈 Vue 3 应用。

核心知识点

1. Nuxt 3 基本概念

1.1 核心特性

Nuxt 3 提供了以下核心特性:

  • 多种渲染模式:支持 SSR、SSG、CSR、边缘渲染等
  • 自动路由:基于文件系统的自动路由生成
  • 状态管理:内置 Pinia 支持
  • API 集成:内置 API 路由和服务器中间件
  • 类型安全:完全支持 TypeScript
  • 性能优化:自动代码分割、懒加载、缓存等
  • 开发体验:热模块替换、自动导入、组件自动注册等

1.2 项目结构

├── app/                     # 应用配置
│   ├── components/          # 自动注册组件
│   ├── composables/         # 自动导入组合式函数
│   ├── middleware/          # 应用级中间件
│   ├── plugins/             # 插件
│   ├── layouts/             # 布局组件
│   ├── pages/               # 页面组件(自动路由)
│   └── error.vue            # 全局错误页面
├── assets/                  # 静态资源
├── public/                  # 公共文件
├── server/                  # 服务器端代码
│   ├── api/                 # API 路由
│   ├── middleware/          # 服务器中间件
│   └── utils/               # 服务器工具函数
├── nuxt.config.ts           # Nuxt 配置
├── package.json             # 项目依赖
└── tsconfig.json            # TypeScript 配置

2. 创建 Nuxt 3 项目

2.1 使用 nuxi 脚手架

# 使用 nuxi 创建项目
npx nuxi@latest init my-nuxt3-app

# 进入项目目录
cd my-nuxt3-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

2.2 基本配置

// nuxt.config.ts
export default defineNuxtConfig({
  // 应用配置
  app: {
    head: {
      title: 'My Nuxt 3 App',
      meta: [
        { charset: 'utf-8' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
        { name: 'description', content: 'My Nuxt 3 App' }
      ]
    }
  },
  // 构建配置
  build: {
    transpile: ['some-package']
  },
  // 模块配置
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt'
  ],
  // 运行时配置
  runtimeConfig: {
    public: {
      apiBase: '/api'
    }
  }
})

3. 路由系统

3.1 自动路由

Nuxt 3 基于文件系统自动生成路由:

pages/
├── index.vue              # /
├── about.vue              # /about
├── posts/                 # /posts
│   ├── index.vue          # /posts
│   └── [id].vue           # /posts/:id
└── users/                 # /users
    ├── [id]/              # /users/:id
    │   ├── index.vue      # /users/:id
    │   └── settings.vue   # /users/:id/settings
    └── [...slug].vue      # /users/*

3.2 动态路由

<!-- pages/posts/[id].vue -->
<template>
  <div class="post">
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script setup>
const route = useRoute()
const post = ref(null)

// 获取路由参数
const id = route.params.id

// 异步获取数据
const { data: post } = await useFetch(`/api/posts/${id}`)
</script>

3.3 嵌套路由

<!-- pages/users/[id]/index.vue -->
<template>
  <div class="user">
    <h1>{{ user.name }}</h1>
    <router-view />
  </div>
</template>

<!-- pages/users/[id]/settings.vue -->
<template>
  <div class="settings">
    <h2>User Settings</h2>
    <!-- 设置表单 -->
  </div>
</template>

4. 数据获取

4.1 useFetch 组合式函数

<!-- pages/index.vue -->
<template>
  <div class="home">
    <h1>Home Page</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <h2>Posts</h2>
      <ul>
        <li v-for="post in posts" :key="post.id">
          <NuxtLink :to="`/posts/${post.id}`">{{ post.title }}</NuxtLink>
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
// 使用 useFetch 获取数据
const { data: posts, pending, error } = await useFetch('/api/posts')
</script>

4.2 useAsyncData 组合式函数

<!-- pages/posts/[id].vue -->
<template>
  <div class="post">
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script setup>
const route = useRoute()
const id = route.params.id

// 使用 useAsyncData 获取数据
const { data: post } = await useAsyncData(`post-${id}`, async () => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
  return response.json()
})
</script>

4.3 $fetch 工具函数

// composables/useApi.js
export function useApi() {
  async function getPosts() {
    return await $fetch('/api/posts')
  }
  
  async function getPost(id) {
    return await $fetch(`/api/posts/${id}`)
  }
  
  return {
    getPosts,
    getPost
  }
}

5. 状态管理

5.1 内置 Pinia 支持

# 安装 Pinia 支持
npm install @pinia/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt']
})

5.2 创建 Store

// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Counter'
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

5.3 使用 Store

<!-- pages/counter.vue -->
<template>
  <div class="counter">
    <h1>{{ store.name }}</h1>
    <p>Count: {{ store.count }}</p>
    <p>Double Count: {{ store.doubleCount }}</p>
    <button @click="store.increment">Increment</button>
  </div>
</template>

<script setup>
// 自动导入 Store
const store = useCounterStore()
</script>

6. API 路由

6.1 基本 API 路由

// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
  // 获取查询参数
  const { limit = 10 } = getQuery(event)
  
  // 返回数据
  return {
    posts: [
      { id: 1, title: 'Post 1', content: 'Content 1' },
      { id: 2, title: 'Post 2', content: 'Content 2' }
    ].slice(0, parseInt(limit))
  }
})

6.2 动态 API 路由

// server/api/posts/[id].get.ts
export default defineEventHandler(async (event) => {
  // 获取路由参数
  const { id } = event.context.params
  
  // 模拟数据库查询
  const posts = [
    { id: 1, title: 'Post 1', content: 'Content 1' },
    { id: 2, title: 'Post 2', content: 'Content 2' }
  ]
  
  const post = posts.find(p => p.id === parseInt(id))
  
  if (!post) {
    // 返回 404 错误
    setResponseStatus(event, 404)
    return { error: 'Post not found' }
  }
  
  return post
})

6.3 POST 请求

// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
  // 获取请求体
  const body = await readBody(event)
  
  // 模拟创建帖子
  return {
    id: Math.random(),
    title: body.title,
    content: body.content,
    createdAt: new Date().toISOString()
  }
})

7. 中间件

7.1 客户端中间件

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // 获取 Pinia Store
  const userStore = useUserStore()
  
  // 检查用户是否认证
  if (!userStore.isAuthenticated && to.path !== '/login') {
    return navigateTo('/login')
  }
})
<!-- pages/dashboard.vue -->
<template>
  <div class="dashboard">
    <h1>Dashboard</h1>
    <!-- 仪表盘内容 -->
  </div>
</template>

<script setup>
// 为页面添加中间件
definePageMeta({
  middleware: ['auth']
})
</script>

7.2 服务器中间件

// server/middleware/auth.ts
export default defineEventHandler((event) => {
  // 获取请求头
  const authorization = getRequestHeader(event, 'authorization')
  
  // 检查授权头
  if (!authorization) {
    setResponseStatus(event, 401)
    return { error: 'Unauthorized' }
  }
  
  // 验证 token
  // ...
})

8. 插件

8.1 创建插件

// plugins/axios.ts
import axios from 'axios'

export default defineNuxtPlugin(() => {
  // 创建 axios 实例
  const axiosInstance = axios.create({
    baseURL: 'https://api.example.com'
  })
  
  // 添加请求拦截器
  axiosInstance.interceptors.request.use(config => {
    // 添加 token
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  })
  
  // 返回插件
  return {
    provide: {
      axios: axiosInstance
    }
  }
})

8.2 使用插件

<!-- pages/index.vue -->
<template>
  <div class="home">
    <h1>Home Page</h1>
    <button @click="fetchData">Fetch Data</button>
  </div>
</template>

<script setup>
// 使用插件
const { $axios } = useNuxtApp()

async function fetchData() {
  try {
    const response = await $axios.get('/posts')
    console.log(response.data)
  } catch (error) {
    console.error(error)
  }
}
</script>

9. 布局组件

9.1 创建布局

<!-- app/layouts/default.vue -->
<template>
  <div class="layout">
    <header>
      <NuxtLink to="/">Home</NuxtLink>
      <NuxtLink to="/about">About</NuxtLink>
      <NuxtLink to="/dashboard">Dashboard</NuxtLink>
    </header>
    
    <main>
      <NuxtPage />
    </main>
    
    <footer>
      <p>© 2024 My Nuxt 3 App</p>
    </footer>
  </div>
</template>

9.2 使用布局

<!-- pages/index.vue -->
<template>
  <div class="home">
    <h1>Home Page</h1>
    <!-- 页面内容 -->
  </div>
</template>

<script setup>
// 指定页面使用的布局
definePageMeta({
  layout: 'default' // 可选,默认使用 default 布局
})
</script>

10. 错误处理

10.1 全局错误页面

<!-- app/error.vue -->
<template>
  <div class="error">
    <h1>{{ error.statusCode }} - {{ error.statusMessage }}</h1>
    <p>{{ error.message }}</p>
    <NuxtLink to="/">Go to Home</NuxtLink>
  </div>
</template>

<script setup>
const props = defineProps({
  error: {
    type: Object,
    required: true
  }
})
</script>

10.2 页面级错误处理

<!-- pages/posts/[id].vue -->
<template>
  <div class="post">
    <template v-if="post">
      <h1>{{ post.title }}</h1>
      <p>{{ post.content }}</p>
    </template>
    <template v-else>
      <h1>Post Not Found</h1>
      <p>The post you are looking for does not exist.</p>
      <NuxtLink to="/posts">Go to Posts</NuxtLink>
    </template>
  </div>
</template>

<script setup>
const route = useRoute()
const id = route.params.id

// 使用 try-catch 处理错误
try {
  const { data: post } = await useAsyncData(`post-${id}`, async () => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
    if (!response.ok) {
      throw new Error('Post not found')
    }
    return response.json()
  })
} catch (error) {
  console.error(error)
  // 可以在这里处理错误
}
</script>

11. 部署

11.1 构建生产版本

# 构建生产版本
npm run build

# 启动生产服务器
npm run preview

11.2 静态站点生成

# 生成静态站点
npm run generate

# 预览静态站点
npm run preview

11.3 部署到 Vercel

# 安装 Vercel CLI
npm install -g vercel

# 部署到 Vercel
vercel

11.4 部署到 Netlify

# 安装 Netlify CLI
npm install -g netlify-cli

# 部署到 Netlify
netlify deploy

最佳实践

1. 代码组织

  • 使用自动导入:利用 Nuxt 3 的自动导入功能,减少手动导入
  • 组件拆分:将复杂组件拆分为多个子组件
  • 组合式函数:使用 composables 封装可复用逻辑
  • API 模块化:将 API 调用封装到 composables 中

2. 性能优化

  • 使用 SSG/SSR:根据需求选择合适的渲染模式
  • 按需加载:使用动态导入和懒加载
  • 缓存策略:合理使用缓存,减少重复请求
  • 图像优化:使用 &lt;NuxtImg&gt; 组件优化图像
  • 字体优化:使用 &lt;NuxtFont&gt; 组件优化字体加载

3. 类型安全

  • 使用 TypeScript:为所有代码添加类型定义
  • 接口定义:为 API 响应和状态定义接口
  • 类型检查:使用 npm run typecheck 进行类型检查

4. 开发体验

  • 使用 Volar:安装 Volar 扩展获得更好的开发体验
  • 利用热模块替换:使用 npm run dev 启动开发服务器,享受热模块替换
  • 使用 Nuxt DevTools:安装 Nuxt DevTools 获得更好的调试体验

5. 安全最佳实践

  • 验证输入:验证所有用户输入
  • 使用 HTTPS:在生产环境中使用 HTTPS
  • 保护敏感数据:不要在客户端存储敏感数据
  • 使用环境变量:使用 .env 文件存储环境变量
  • 限制 API 访问:为 API 添加适当的访问控制

常见问题和解决方案

1. 自动导入不工作

问题:自动导入的组件或函数无法使用

解决方案

  • 检查文件名和目录结构是否正确
  • 重启开发服务器
  • 检查 nuxt.config.ts 配置
  • 确保组件和函数使用了正确的命名约定

2. API 路由 404

问题:API 路由返回 404

解决方案

  • 检查 API 路由文件路径是否正确
  • 检查 HTTP 方法是否正确(get、post 等)
  • 检查请求 URL 是否正确
  • 查看服务器日志获取更多信息

3. 构建失败

问题:构建生产版本失败

解决方案

  • 检查 TypeScript 类型错误
  • 检查依赖版本是否兼容
  • 查看构建日志获取更多信息
  • 尝试清理缓存:npx nuxi clean

4. 渲染模式问题

问题:渲染模式不符合预期

解决方案

  • 检查 nuxt.config.ts 中的 ssr 配置
  • 为页面添加 definePageMeta({ ssr: false }) 配置
  • 检查是否使用了浏览器特定的 API

5. 性能问题

问题:应用性能不佳

解决方案

  • 使用 Lighthouse 进行性能审计
  • 优化图像和字体
  • 减少 HTTP 请求
  • 合理使用缓存
  • 优化组件渲染

进阶学习资源

1. 官方文档

2. 工具和库

3. 最佳实践指南

4. 学习案例

实践练习

练习 1:创建 Nuxt 3 项目

  1. 使用 nuxi 创建一个 Nuxt 3 项目
  2. 配置基本选项
  3. 启动开发服务器
  4. 熟悉项目结构

练习 2:实现自动路由

  1. 创建 pages 目录
  2. 创建几个页面组件
  3. 测试自动路由生成
  4. 实现动态路由

练习 3:数据获取

  1. 使用 useFetch 获取数据
  2. 使用 useAsyncData 获取数据
  3. 实现 API 路由
  4. 测试数据获取功能

练习 4:状态管理

  1. 安装并配置 Pinia
  2. 创建一个计数器 Store
  3. 在组件中使用 Store
  4. 测试状态管理功能

练习 5:中间件和插件

  1. 创建客户端中间件
  2. 创建服务器中间件
  3. 创建插件
  4. 测试中间件和插件功能

练习 6:部署项目

  1. 构建生产版本
  2. 生成静态站点
  3. 部署到 Vercel 或 Netlify
  4. 测试部署后的应用

总结

Nuxt 3 是基于 Vue 3 的全栈框架,提供了多种渲染模式、自动路由、状态管理、API 集成等功能,帮助开发者构建现代化、高性能的 Web 应用。通过掌握 Nuxt 3 的核心概念和使用方法,你可以快速构建可扩展、可维护的全栈应用。在实际开发中,我们需要根据项目需求选择合适的渲染模式、优化性能、确保类型安全,并遵循最佳实践,构建出高质量的 Nuxt 3 应用。

下一集我们将学习 Vue 3 与 Astro 静态站点生成,敬请期待!

« 上一篇 Vue 3与Vue Router高级路由模式 - 现代化导航系统解决方案 下一篇 » Vue 3与Astro静态站点生成 - 高性能静态站点解决方案