Nuxt.js项目架构 #
一、项目结构 #
1.1 推荐目录结构 #
text
my-nuxt-app/
├── .nuxt/ # 自动生成的构建文件
├── .output/ # 构建输出
├── app.vue # 应用入口
├── nuxt.config.ts # Nuxt 配置
├── app.config.ts # 应用配置
├── error.vue # 错误页面
│
├── assets/ # 需要构建的资源
│ ├── css/
│ │ ├── main.css
│ │ └── variables.css
│ ├── images/
│ └── fonts/
│
├── components/ # 组件
│ ├── base/ # 基础组件
│ │ ├── Button.vue
│ │ ├── Input.vue
│ │ └── Icon.vue
│ ├── common/ # 通用组件
│ │ ├── Header.vue
│ │ ├── Footer.vue
│ │ └── Sidebar.vue
│ ├── features/ # 功能组件
│ │ ├── SearchBar.vue
│ │ └── UserMenu.vue
│ └── ui/ # UI 组件
│ ├── Modal.vue
│ ├── Toast.vue
│ └── Dropdown.vue
│
├── composables/ # 组合式函数
│ ├── useAuth.ts
│ ├── useCart.ts
│ ├── useTheme.ts
│ └── features/
│ ├── useSearch.ts
│ └── useFilter.ts
│
├── layouts/ # 布局
│ ├── default.vue
│ ├── admin.vue
│ └── auth.vue
│
├── middleware/ # 路由中间件
│ ├── auth.ts
│ ├── admin.ts
│ └── guest.ts
│
├── modules/ # 本地模块
│ └── my-module/
│ ├── index.ts
│ └── runtime/
│
├── pages/ # 页面
│ ├── index.vue
│ ├── about.vue
│ ├── auth/
│ │ ├── login.vue
│ │ └── register.vue
│ ├── admin/
│ │ ├── index.vue
│ │ └── users.vue
│ └── blog/
│ ├── index.vue
│ └── [slug].vue
│
├── plugins/ # 插件
│ ├── api.ts
│ ├── dayjs.ts
│ └── directives.ts
│
├── public/ # 静态文件
│ ├── favicon.ico
│ ├── robots.txt
│ └── images/
│
├── server/ # 服务端代码
│ ├── api/
│ │ ├── auth/
│ │ ├── users/
│ │ └── blog/
│ ├── middleware/
│ │ └── auth.ts
│ ├── routes/
│ │ └── sitemap.xml.ts
│ └── utils/
│ ├── db.ts
│ └── auth.ts
│
├── stores/ # Pinia 状态
│ ├── auth.ts
│ ├── cart.ts
│ └── app.ts
│
├── types/ # 类型定义
│ ├── index.d.ts
│ ├── api.d.ts
│ └── models.d.ts
│
├── utils/ # 工具函数
│ ├── format.ts
│ ├── validate.ts
│ └── constants.ts
│
├── .env # 环境变量
├── .env.example # 环境变量示例
├── .gitignore
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
└── tailwind.config.js
1.2 模块化组织 #
text
modules/
├── blog/
│ ├── components/
│ │ ├── BlogCard.vue
│ │ └── BlogList.vue
│ ├── composables/
│ │ └── useBlog.ts
│ ├── pages/
│ │ └── blog/
│ ├── server/
│ │ └── api/blog/
│ └── stores/
│ └── blog.ts
└── shop/
├── components/
├── composables/
├── pages/
├── server/
└── stores/
二、命名规范 #
2.1 文件命名 #
| 类型 | 命名规范 | 示例 |
|---|---|---|
| 页面 | kebab-case | user-profile.vue |
| 组件 | PascalCase | UserProfile.vue |
| 组合式函数 | camelCase + use | useAuth.ts |
| 工具函数 | camelCase | formatDate.ts |
| 类型 | PascalCase | User.ts |
| 常量 | UPPER_SNAKE_CASE | API_BASE_URL.ts |
2.2 组件命名 #
text
components/
├── base/
│ ├── BaseButton.vue → <BaseButton />
│ └── BaseInput.vue → <BaseInput />
├── blog/
│ ├── BlogCard.vue → <BlogCard />
│ └── BlogList.vue → <BlogList />
└── user/
├── UserAvatar.vue → <UserAvatar />
└── UserMenu.vue → <UserMenu />
2.3 组合式函数命名 #
typescript
export const useAuth = () => {}
export const useCart = () => {}
export const useTheme = () => {}
export const useSearch = () => {}
三、代码组织 #
3.1 组件结构 #
vue
<template>
<div class="component">
</div>
</template>
<script setup lang="ts">
interface Props {
title: string
count?: number
}
interface Emits {
(e: 'update', value: string): void
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
const emit = defineEmits<Emits>()
const localState = ref('')
const computedValue = computed(() => props.count * 2)
const handleClick = () => {
emit('update', localState.value)
}
onMounted(() => {
})
</script>
<style scoped>
.component {
}
</style>
3.2 组合式函数结构 #
typescript
interface UseFeatureOptions {
immediate?: boolean
}
interface UseFeatureReturn {
data: Ref<any>
loading: Ref<boolean>
error: Ref<Error | null>
execute: () => Promise<void>
}
export const useFeature = (options: UseFeatureOptions = {}): UseFeatureReturn => {
const { immediate = true } = options
const data = ref(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const execute = async () => {
loading.value = true
error.value = null
try {
const result = await fetchData()
data.value = result
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
if (immediate) {
execute()
}
return {
data,
loading,
error,
execute
}
}
3.3 API路由结构 #
typescript
import { z } from 'zod'
const schema = z.object({
name: z.string().min(1),
email: z.string().email()
})
export default defineEventHandler(async (event) => {
const method = getMethod(event)
switch (method) {
case 'GET':
return await handleGet(event)
case 'POST':
return await handlePost(event)
default:
throw createError({
statusCode: 405,
message: 'Method Not Allowed'
})
}
})
async function handleGet(event: H3Event) {
const query = getQuery(event)
const data = await fetchData(query)
return { data }
}
async function handlePost(event: H3Event) {
const body = await readValidatedBody(event, schema.parse)
const result = await createData(body)
return { data: result }
}
四、类型管理 #
4.1 全局类型 #
types/index.d.ts:
typescript
interface User {
id: number
name: string
email: string
role: 'admin' | 'user'
createdAt: string
}
interface Post {
id: number
title: string
content: string
author: User
tags: string[]
createdAt: string
}
interface ApiResponse<T> {
data: T
total?: number
page?: number
pageSize?: number
}
4.2 API类型 #
types/api.d.ts:
typescript
interface LoginRequest {
email: string
password: string
}
interface LoginResponse {
user: User
token: string
}
interface CreatePostRequest {
title: string
content: string
tags?: string[]
}
interface UpdatePostRequest extends Partial<CreatePostRequest> {
id: number
}
4.3 组件Props类型 #
typescript
interface UserCardProps {
user: User
showActions?: boolean
compact?: boolean
}
interface UserCardEmits {
(e: 'edit', user: User): void
(e: 'delete', id: number): void
}
五、错误处理 #
5.1 全局错误处理 #
plugins/error-handler.ts:
typescript
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:error', (error) => {
console.error('Global error:', error)
const { $notify } = useNuxtApp()
$notify?.error('An error occurred')
})
nuxtApp.hook('vue:error', (error, instance, info) => {
console.error('Vue error:', error, info)
})
})
5.2 API错误处理 #
typescript
export const useApi = () => {
const { $notify } = useNuxtApp()
const fetch = async <T>(url: string, options: any = {}): Promise<T> => {
try {
return await $fetch<T>(url, options)
} catch (error: any) {
if (error.response?.status === 401) {
navigateTo('/login')
} else {
$notify?.error(error.message || 'Request failed')
}
throw error
}
}
return { fetch }
}
5.3 错误边界 #
error.vue:
vue
<template>
<div class="error-page">
<h1>{{ error.statusCode }}</h1>
<p>{{ error.message }}</p>
<button @click="handleError">返回首页</button>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
error: {
statusCode: number
message: string
}
}>()
const handleError = () => clearError({ redirect: '/' })
</script>
六、最佳实践 #
6.1 组件设计原则 #
- 单一职责:每个组件只做一件事
- 可复用:组件应该是可复用的
- 可测试:组件应该易于测试
- Props向下,Events向上
6.2 状态管理原则 #
- 就近原则:状态放在最近的共同父组件
- 简单状态:使用 ref/reactive
- 跨组件:使用 useState
- 复杂状态:使用 Pinia
6.3 API设计原则 #
- RESTful 风格
- 统一响应格式
- 错误处理一致
- 类型安全
七、总结 #
本章介绍了 Nuxt.js 项目架构:
- 推荐的目录结构
- 命名规范
- 代码组织方式
- 类型管理
- 错误处理
- 最佳实践
良好的项目架构是可维护性的基础,下一章我们将学习 TypeScript 集成。
最后更新:2026-03-28