如何高效地组织一个大型 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 组织
- 类型定义
最佳实践:
- 文件命名规范
- 导入导出规范
- 环境配置
- 代码审查
注意事项:
- 保持结构清晰
- 避免过度抽象
- 考虑团队协作
- 定期重构