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 dev2.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 preview11.2 静态站点生成
# 生成静态站点
npm run generate
# 预览静态站点
npm run preview11.3 部署到 Vercel
# 安装 Vercel CLI
npm install -g vercel
# 部署到 Vercel
vercel11.4 部署到 Netlify
# 安装 Netlify CLI
npm install -g netlify-cli
# 部署到 Netlify
netlify deploy最佳实践
1. 代码组织
- 使用自动导入:利用 Nuxt 3 的自动导入功能,减少手动导入
- 组件拆分:将复杂组件拆分为多个子组件
- 组合式函数:使用 composables 封装可复用逻辑
- API 模块化:将 API 调用封装到 composables 中
2. 性能优化
- 使用 SSG/SSR:根据需求选择合适的渲染模式
- 按需加载:使用动态导入和懒加载
- 缓存策略:合理使用缓存,减少重复请求
- 图像优化:使用
<NuxtImg>组件优化图像 - 字体优化:使用
<NuxtFont>组件优化字体加载
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 项目
- 使用 nuxi 创建一个 Nuxt 3 项目
- 配置基本选项
- 启动开发服务器
- 熟悉项目结构
练习 2:实现自动路由
- 创建 pages 目录
- 创建几个页面组件
- 测试自动路由生成
- 实现动态路由
练习 3:数据获取
- 使用 useFetch 获取数据
- 使用 useAsyncData 获取数据
- 实现 API 路由
- 测试数据获取功能
练习 4:状态管理
- 安装并配置 Pinia
- 创建一个计数器 Store
- 在组件中使用 Store
- 测试状态管理功能
练习 5:中间件和插件
- 创建客户端中间件
- 创建服务器中间件
- 创建插件
- 测试中间件和插件功能
练习 6:部署项目
- 构建生产版本
- 生成静态站点
- 部署到 Vercel 或 Netlify
- 测试部署后的应用
总结
Nuxt 3 是基于 Vue 3 的全栈框架,提供了多种渲染模式、自动路由、状态管理、API 集成等功能,帮助开发者构建现代化、高性能的 Web 应用。通过掌握 Nuxt 3 的核心概念和使用方法,你可以快速构建可扩展、可维护的全栈应用。在实际开发中,我们需要根据项目需求选择合适的渲染模式、优化性能、确保类型安全,并遵循最佳实践,构建出高质量的 Nuxt 3 应用。
下一集我们将学习 Vue 3 与 Astro 静态站点生成,敬请期待!