如何高效地组织一个大型 Next.js 项目的代码结构?

53 阅读4分钟

如何高效地组织一个大型 Next.js 项目的代码结构?

项目结构设计原则

1. 功能模块化

# 推荐的项目结构
src/
├── app/                    # App Router 页面
│   ├── (auth)/            # 路由组
│   │   ├── login/
│   │   └── register/
│   ├── (dashboard)/       # 路由组
│   │   ├── dashboard/
│   │   └── profile/
│   ├── api/               # API 路由
│   │   ├── auth/
│   │   ├── users/
│   │   └── products/
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── components/            # 可复用组件
│   ├── ui/               # 基础 UI 组件
│   │   ├── Button/
│   │   ├── Input/
│   │   └── Modal/
│   ├── forms/            # 表单组件
│   ├── layout/           # 布局组件
│   └── features/         # 功能组件
│       ├── auth/
│       ├── dashboard/
│       └── products/
├── lib/                  # 工具库
│   ├── auth.ts
│   ├── database.ts
│   ├── utils.ts
│   └── validations.ts
├── hooks/                # 自定义 Hook
│   ├── useAuth.ts
│   ├── useLocalStorage.ts
│   └── useApi.ts
├── store/                # 状态管理
│   ├── authStore.ts
│   ├── userStore.ts
│   └── productStore.ts
├── types/                # TypeScript 类型
│   ├── auth.ts
│   ├── user.ts
│   └── product.ts
└── constants/            # 常量
    ├── routes.ts
    ├── api.ts
    └── config.ts

2. 按功能域组织

# 按功能域组织的结构
src/
├── app/
├── features/             # 功能模块
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── store/
│   │   ├── types/
│   │   └── utils/
│   ├── dashboard/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── store/
│   │   ├── types/
│   │   └── utils/
│   └── products/
│       ├── components/
│       ├── hooks/
│       ├── store/
│       ├── types/
│       └── utils/
├── shared/               # 共享资源
│   ├── components/
│   ├── hooks/
│   ├── utils/
│   └── types/
└── lib/                  # 核心库
    ├── auth.ts
    ├── database.ts
    └── utils.ts

组件组织策略

1. 组件分层

// 基础 UI 组件
// src/components/ui/Button/Button.tsx
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  children: React.ReactNode
  onClick?: () => void
}

export default function Button({
  variant = 'primary',
  size = 'md',
  children,
  onClick
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
    >
      {children}
    </button>
  )
}

// 功能组件
// src/components/features/auth/LoginForm.tsx
'use client'
import { useState } from 'react'
import Button from '@/components/ui/Button/Button'
import Input from '@/components/ui/Input/Input'

export default function LoginForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: ''
  })

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    // 处理登录逻辑
  }

  return (
    <form onSubmit={handleSubmit}>
      <Input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({...formData, email: e.target.value})}
        placeholder="Email"
      />
      <Input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({...formData, password: e.target.value})}
        placeholder="Password"
      />
      <Button type="submit">Login</Button>
    </form>
  )
}

// 页面组件
// src/app/(auth)/login/page.tsx
import LoginForm from '@/components/features/auth/LoginForm'

export default function LoginPage() {
  return (
    <div>
      <h1>Login</h1>
      <LoginForm />
    </div>
  )
}

2. 组件组合

// 复合组件
// src/components/features/dashboard/DashboardLayout.tsx
import { ReactNode } from 'react'
import Sidebar from './Sidebar'
import Header from './Header'
import Footer from './Footer'

interface DashboardLayoutProps {
  children: ReactNode
}

export default function DashboardLayout({ children }: DashboardLayoutProps) {
  return (
    <div className="dashboard-layout">
      <Header />
      <div className="dashboard-content">
        <Sidebar />
        <main className="dashboard-main">
          {children}
        </main>
      </div>
      <Footer />
    </div>
  )
}

// 使用复合组件
// src/app/(dashboard)/dashboard/page.tsx
import DashboardLayout from '@/components/features/dashboard/DashboardLayout'
import StatsCards from '@/components/features/dashboard/StatsCards'
import RecentActivity from '@/components/features/dashboard/RecentActivity'

export default function DashboardPage() {
  return (
    <DashboardLayout>
      <StatsCards />
      <RecentActivity />
    </DashboardLayout>
  )
}

状态管理组织

1. 状态分离

// 认证状态
// src/store/authStore.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface AuthState {
  user: User | null
  token: string | null
  isAuthenticated: boolean
  login: (credentials: LoginCredentials) => Promise<void>
  logout: () => void
  updateUser: (userData: Partial<User>) => void
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isAuthenticated: false,

      login: async (credentials) => {
        // 登录逻辑
      },

      logout: () => {
        set({ user: null, token: null, isAuthenticated: false })
      },

      updateUser: (userData) => {
        const currentUser = get().user
        if (currentUser) {
          set({ user: { ...currentUser, ...userData } })
        }
      }
    }),
    {
      name: 'auth-storage'
    }
  )
)

// 用户状态
// src/store/userStore.ts
import { create } from 'zustand'

interface UserState {
  profile: UserProfile | null
  preferences: UserPreferences | null
  updateProfile: (profile: Partial<UserProfile>) => void
  updatePreferences: (preferences: Partial<UserPreferences>) => void
}

export const useUserStore = create<UserState>((set) => ({
  profile: null,
  preferences: null,

  updateProfile: (profile) => {
    set((state) => ({
      profile: { ...state.profile, ...profile }
    }))
  },

  updatePreferences: (preferences) => {
    set((state) => ({
      preferences: { ...state.preferences, ...preferences }
    }))
  }
}))

2. 状态组合

// 状态组合 Hook
// src/hooks/useAuth.ts
import { useAuthStore } from '@/store/authStore'
import { useUserStore } from '@/store/userStore'

export function useAuth() {
  const auth = useAuthStore()
  const user = useUserStore()

  return {
    ...auth,
    ...user,
    isReady: auth.isAuthenticated && user.profile !== null,
  }
}

API 组织

1. API 客户端

// API 客户端
// src/lib/api/client.ts
class ApiClient {
  private baseURL: string
  private token: string | null = null

  constructor(baseURL: string) {
    this.baseURL = baseURL
  }

  setToken(token: string) {
    this.token = token
  }

  async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const url = `${this.baseURL}${endpoint}`
    const headers = {
      'Content-Type': 'application/json',
      ...(this.token && { Authorization: `Bearer ${this.token}` }),
      ...options.headers
    }

    const response = await fetch(url, {
      ...options,
      headers
    })

    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`)
    }

    return response.json()
  }
}

export const apiClient = new ApiClient(process.env.NEXT_PUBLIC_API_URL || '')

2. API 服务

// 认证服务
// src/lib/api/auth.ts
import { apiClient } from './client'

export const authService = {
  login: async (credentials: LoginCredentials) => {
    return (
      apiClient.request <
      AuthResponse >
      ('/auth/login',
      {
        method: 'POST',
        body: JSON.stringify(credentials),
      })
    )
  },

  register: async (userData: RegisterData) => {
    return (
      apiClient.request <
      AuthResponse >
      ('/auth/register',
      {
        method: 'POST',
        body: JSON.stringify(userData),
      })
    )
  },

  logout: async () => {
    return apiClient.request('/auth/logout', {
      method: 'POST',
    })
  },
}

// 用户服务
// src/lib/api/user.ts
import { apiClient } from './client'

export const userService = {
  getProfile: async () => {
    return apiClient.request < UserProfile > '/user/profile'
  },

  updateProfile: async (profile: Partial<UserProfile>) => {
    return (
      apiClient.request <
      UserProfile >
      ('/user/profile',
      {
        method: 'PUT',
        body: JSON.stringify(profile),
      })
    )
  },
}

类型定义组织

1. 类型分离

// 基础类型
// src/types/common.ts
export interface BaseEntity {
  id: string
  createdAt: Date
  updatedAt: Date
}

export interface PaginationParams {
  page: number
  limit: number
}

export interface PaginatedResponse<T> {
  data: T[]
  total: number
  page: number
  limit: number
}

// 认证类型
// src/types/auth.ts
export interface User extends BaseEntity {
  email: string
  name: string
  role: UserRole
}

export interface LoginCredentials {
  email: string
  password: string
}

export interface AuthResponse {
  user: User
  token: string
}

// 用户类型
// src/types/user.ts
export interface UserProfile extends BaseEntity {
  userId: string
  firstName: string
  lastName: string
  avatar?: string
  bio?: string
}

export interface UserPreferences extends BaseEntity {
  userId: string
  theme: 'light' | 'dark'
  language: string
  notifications: NotificationSettings
}

2. 类型组合

// 类型组合
// src/types/index.ts
export * from './common'
export * from './auth'
export * from './user'
export * from './product'

// 使用类型组合
// src/components/features/user/UserProfile.tsx
import { UserProfile, UserPreferences } from '@/types'

interface UserProfileProps {
  profile: UserProfile
  preferences: UserPreferences
  onUpdate: (data: Partial<UserProfile>) => void
}

工具函数组织

1. 工具函数分类

// 通用工具
// src/lib/utils/common.ts
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('en-US').format(date)
}

export function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount)
}

export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout
  return (...args: Parameters<T>) => {
    clearTimeout(timeout)
    timeout = setTimeout(() => func(...args), wait)
  }
}

// 验证工具
// src/lib/utils/validation.ts
import { z } from 'zod'

export const emailSchema = z.string().email()
export const passwordSchema = z.string().min(8)

export function validateEmail(email: string): boolean {
  return emailSchema.safeParse(email).success
}

export function validatePassword(password: string): boolean {
  return passwordSchema.safeParse(password).success
}

// 格式化工具
// src/lib/utils/format.ts
export function formatFileSize(bytes: number): string {
  const sizes = ['Bytes', 'KB', 'MB', 'GB']
  if (bytes === 0) return '0 Bytes'
  const i = Math.floor(Math.log(bytes) / Math.log(1024))
  return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
}

2. 工具函数组合

// 工具函数组合
// src/lib/utils/index.ts
export * from './common'
export * from './validation'
export * from './format'

// 使用工具函数
// src/components/features/user/UserCard.tsx
import { formatDate, formatCurrency } from '@/lib/utils'

export default function UserCard({ user }: { user: User }) {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>Joined: {formatDate(user.createdAt)}</p>
      <p>Balance: {formatCurrency(user.balance)}</p>
    </div>
  )
}

最佳实践

1. 文件命名规范

# 组件文件
Button.tsx              # 默认导出组件
Button.stories.tsx      # Storybook 故事
Button.test.tsx         # 测试文件
Button.module.css       # CSS 模块

# 工具文件
utils.ts                # 工具函数
constants.ts            # 常量
types.ts                # 类型定义

2. 导入导出规范

// 使用命名导出
export const Button = () => {
  /* ... */
}
export const Input = () => {
  /* ... */
}

// 使用默认导出
export default function Dashboard() {
  /* ... */
}

// 使用索引文件
// src/components/ui/index.ts
export { Button } from './Button'
export { Input } from './Input'
export { Modal } from './Modal'

// 使用
import { Button, Input } from '@/components/ui'

3. 环境配置

// 环境配置
// src/lib/config.ts
export const config = {
  api: {
    baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
    timeout: 10000,
  },
  auth: {
    tokenKey: 'auth-token',
    refreshTokenKey: 'refresh-token',
  },
  app: {
    name: 'My App',
    version: '1.0.0',
  },
}

总结

大型 Next.js 项目组织要点:

结构设计

  • 功能模块化
  • 按功能域组织
  • 组件分层

组织策略

  • 组件组合
  • 状态分离
  • API 组织
  • 类型定义

最佳实践

  • 文件命名规范
  • 导入导出规范
  • 环境配置
  • 代码审查

注意事项

  • 保持结构清晰
  • 避免过度抽象
  • 考虑团队协作
  • 定期重构