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