Next.js项目结构 #

一、基础目录结构 #

1.1 默认项目结构 #

text
my-nextjs-app/
├── src/
│   └── app/
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
├── public/
│   └── images/
├── .eslintrc.json
├── .gitignore
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
└── tsconfig.json

1.2 推荐的完整结构 #

text
my-nextjs-app/
├── src/
│   ├── app/                    # App Router 目录
│   │   ├── (auth)/             # 路由组:认证相关
│   │   │   ├── login/
│   │   │   │   └── page.tsx
│   │   │   └── register/
│   │   │       └── page.tsx
│   │   ├── (dashboard)/        # 路由组:仪表盘
│   │   │   ├── layout.tsx
│   │   │   ├── dashboard/
│   │   │   │   └── page.tsx
│   │   │   └── settings/
│   │   │       └── page.tsx
│   │   ├── api/                # API 路由
│   │   │   ├── auth/
│   │   │   │   └── route.ts
│   │   │   └── users/
│   │   │       └── route.ts
│   │   ├── blog/               # 博客页面
│   │   │   ├── [slug]/
│   │   │   │   └── page.tsx
│   │   │   └── page.tsx
│   │   ├── components/         # 共享组件
│   │   │   ├── ui/             # UI基础组件
│   │   │   └── features/       # 功能组件
│   │   ├── error.tsx           # 全局错误页面
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   ├── layout.tsx          # 根布局
│   │   ├── loading.tsx         # 全局加载页面
│   │   ├── not-found.tsx       # 404页面
│   │   └── page.tsx            # 首页
│   ├── components/             # 全局共享组件
│   │   ├── ui/
│   │   │   ├── button.tsx
│   │   │   ├── card.tsx
│   │   │   └── input.tsx
│   │   ├── layout/
│   │   │   ├── footer.tsx
│   │   │   ├── header.tsx
│   │   │   └── sidebar.tsx
│   │   └── features/
│   │       ├── auth-form.tsx
│   │       └── user-profile.tsx
│   ├── hooks/                  # 自定义Hooks
│   │   ├── use-auth.ts
│   │   └── use-local-storage.ts
│   ├── lib/                    # 工具库
│   │   ├── api.ts
│   │   ├── auth.ts
│   │   └── utils.ts
│   ├── types/                  # TypeScript类型
│   │   ├── api.ts
│   │   └── index.ts
│   └── styles/                 # 全局样式
│       └── variables.css
├── public/                     # 静态资源
│   ├── fonts/
│   ├── images/
│   │   ├── logo.png
│   │   └── og-image.png
│   └── robots.txt
├── .env.local                  # 本地环境变量
├── .env.example                # 环境变量示例
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── components.json             # shadcn/ui配置
├── middleware.ts               # 中间件
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── tailwind.config.ts
└── tsconfig.json

二、App Router目录详解 #

2.1 特殊文件 #

App Router 定义了一系列特殊文件,用于处理不同的功能:

文件名 用途 说明
page.tsx 页面组件 定义路由页面
layout.tsx 布局组件 共享布局
template.tsx 模板组件 类似布局但会重新创建
loading.tsx 加载状态 显示加载UI
error.tsx 错误处理 处理运行时错误
not-found.tsx 404页面 处理未找到路由
global-error.tsx 全局错误 处理根布局错误
default.tsx 默认页面 并行路由默认页
route.ts API路由 处理API请求
middleware.ts 中间件 请求拦截处理

2.2 页面文件 page.tsx #

tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
    title: '页面标题',
    description: '页面描述',
}

export default function Page() {
    return <div>页面内容</div>
}

2.3 布局文件 layout.tsx #

tsx
export default function Layout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <div className="layout">
            <header>头部</header>
            <main>{children}</main>
            <footer>底部</footer>
        </div>
    )
}

2.4 加载状态 loading.tsx #

tsx
export default function Loading() {
    return (
        <div className="flex items-center justify-center min-h-screen">
            <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
        </div>
    )
}

2.5 错误处理 error.tsx #

tsx
'use client'

import { useEffect } from 'react'

export default function Error({
    error,
    reset,
}: {
    error: Error & { digest?: string }
    reset: () => void
}) {
    useEffect(() => {
        console.error(error)
    }, [error])

    return (
        <div className="flex flex-col items-center justify-center min-h-screen">
            <h2>出错了!</h2>
            <button onClick={() => reset()}>重试</button>
        </div>
    )
}

2.6 404页面 not-found.tsx #

tsx
import Link from 'next/link'

export default function NotFound() {
    return (
        <div className="flex flex-col items-center justify-center min-h-screen">
            <h1 className="text-6xl font-bold">404</h1>
            <p className="text-xl mt-4">页面未找到</p>
            <Link href="/" className="mt-8 text-blue-500 hover:underline">
                返回首页
            </Link>
        </div>
    )
}

三、路由组织 #

3.1 路由段 #

text
app/
├── page.tsx              → /
├── about/
│   └── page.tsx          → /about
├── blog/
│   ├── page.tsx          → /blog
│   └── [slug]/
│       └── page.tsx      → /blog/:slug
└── shop/
    ├── [category]/
    │   └── [id]/
    │       └── page.tsx  → /shop/:category/:id
    └── page.tsx          → /shop

3.2 路由组 #

使用括号创建路由组,不影响URL路径:

text
app/
├── (marketing)/
│   ├── about/
│   │   └── page.tsx      → /about
│   ├── contact/
│   │   └── page.tsx      → /contact
│   └── layout.tsx        # 营销页面共享布局
├── (shop)/
│   ├── cart/
│   │   └── page.tsx      → /cart
│   ├── products/
│   │   └── page.tsx      → /products
│   └── layout.tsx        # 商店页面共享布局
└── layout.tsx            # 根布局

3.3 并行路由 #

使用 @ 前缀创建并行路由:

text
app/
├── dashboard/
│   ├── @team/
│   │   └── page.tsx
│   ├── @analytics/
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx

布局中使用:

tsx
export default function Layout({
    children,
    team,
    analytics,
}: {
    children: React.ReactNode
    team: React.ReactNode
    analytics: React.ReactNode
}) {
    return (
        <div>
            {children}
            <div className="grid grid-cols-2 gap-4">
                {team}
                {analytics}
            </div>
        </div>
    )
}

3.4 拦截路由 #

使用 (.) 前缀拦截同级路由:

text
app/
├── feed/
│   └── page.tsx          → /feed
├── photo/
│   └── [id]/
│       └── page.tsx      → /photo/:id
└── (.)photo/
    └── [id]/
        └── page.tsx      # 拦截 /feed 中的 /photo/:id

四、组件组织 #

4.1 组件分类 #

text
src/components/
├── ui/                   # 基础UI组件
│   ├── button.tsx
│   ├── card.tsx
│   ├── input.tsx
│   ├── select.tsx
│   └── index.ts          # 统一导出
├── layout/               # 布局组件
│   ├── header.tsx
│   ├── footer.tsx
│   ├── sidebar.tsx
│   └── navigation.tsx
├── features/             # 功能组件
│   ├── auth/
│   │   ├── login-form.tsx
│   │   └── register-form.tsx
│   ├── blog/
│   │   ├── post-card.tsx
│   │   └── post-list.tsx
│   └── user/
│       ├── avatar.tsx
│       └── profile.tsx
└── shared/               # 共享组件
    ├── loading-spinner.tsx
    └── error-boundary.tsx

4.2 UI组件示例 #

tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const buttonVariants = cva(
    'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors',
    {
        variants: {
            variant: {
                default: 'bg-primary text-white hover:bg-primary/90',
                outline: 'border border-input bg-background hover:bg-accent',
                ghost: 'hover:bg-accent hover:text-accent-foreground',
            },
            size: {
                default: 'h-10 px-4 py-2',
                sm: 'h-9 rounded-md px-3',
                lg: 'h-11 rounded-md px-8',
            },
        },
        defaultVariants: {
            variant: 'default',
            size: 'default',
        },
    }
)

interface ButtonProps
    extends React.ButtonHTMLAttributes<HTMLButtonElement>,
        VariantProps<typeof buttonVariants> {}

export function Button({ className, variant, size, ...props }: ButtonProps) {
    return (
        <button
            className={cn(buttonVariants({ variant, size, className }))}
            {...props}
        />
    )
}

4.3 组件导出规范 #

tsx
export { Button } from './button'
export { Card } from './card'
export { Input } from './input'
export { Select } from './select'

五、工具库组织 #

5.1 lib目录结构 #

text
src/lib/
├── api.ts                # API请求封装
├── auth.ts               # 认证相关
├── constants.ts          # 常量定义
├── db.ts                 # 数据库连接
├── utils.ts              # 工具函数
└── validations.ts        # 表单验证

5.2 工具函数示例 #

tsx
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs))
}

export function formatDate(date: Date | string): string {
    return new Date(date).toLocaleDateString('zh-CN', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
    })
}

export function slugify(text: string): string {
    return text
        .toLowerCase()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '')
}

5.3 API封装示例 #

tsx
const BASE_URL = process.env.NEXT_PUBLIC_API_URL

interface FetchOptions extends RequestInit {
    params?: Record<string, string>
}

export async function fetchApi<T>(
    endpoint: string,
    options: FetchOptions = {}
): Promise<T> {
    const { params, ...fetchOptions } = options
    
    const url = new URL(`${BASE_URL}${endpoint}`)
    if (params) {
        Object.entries(params).forEach(([key, value]) => {
            url.searchParams.append(key, value)
        })
    }

    const response = await fetch(url.toString(), {
        ...fetchOptions,
        headers: {
            'Content-Type': 'application/json',
            ...fetchOptions.headers,
        },
    })

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

    return response.json()
}

六、类型定义组织 #

6.1 types目录结构 #

text
src/types/
├── api.ts                # API响应类型
├── models.ts             # 数据模型类型
├── components.ts         # 组件Props类型
└── index.ts              # 统一导出

6.2 类型定义示例 #

tsx
export interface User {
    id: string
    name: string
    email: string
    avatar?: string
    createdAt: Date
    updatedAt: Date
}

export interface Post {
    id: string
    title: string
    content: string
    slug: string
    author: User
    tags: string[]
    publishedAt: Date
}

export interface ApiResponse<T> {
    data: T
    message: string
    success: boolean
}

export interface PaginatedResponse<T> extends ApiResponse<T[]> {
    pagination: {
        page: number
        limit: number
        total: number
        totalPages: number
    }
}

七、命名规范 #

7.1 文件命名 #

类型 命名规范 示例
页面 小写 page.tsx
布局 小写 layout.tsx
组件 小写kebab-case user-card.tsx
Hook camelCase useAuth.ts
工具函数 camelCase formatDate.ts
类型 camelCase user.ts
常量 UPPER_SNAKE_CASE API_ENDPOINTS.ts

7.2 组件命名 #

tsx
export function UserCard({ user }: UserCardProps) {}
export function BlogPostList({ posts }: BlogPostListProps) {}
export function NavigationHeader() {}

7.3 目录命名 #

text
src/app/
├── blog/                 # 小写
├── user-profile/         # kebab-case
├── (dashboard)/          # 路由组用括号
└── @team/                # 并行路由用@前缀

八、最佳实践 #

8.1 目录组织原则 #

  1. 按功能分组:相关文件放在一起
  2. 就近原则:组件和样式就近放置
  3. 共享提取:公共组件提取到共享目录
  4. 扁平结构:避免过深的目录嵌套

8.2 导入路径 #

使用别名简化导入:

tsx
import { Button } from '@/components/ui/button'
import { formatDate } from '@/lib/utils'
import { User } from '@/types'

8.3 代码分割 #

大型组件按需加载:

tsx
import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(() => import('./heavy-component'), {
    loading: () => <p>加载中...</p>,
})

九、总结 #

项目结构要点:

要点 说明
App Router 使用app目录组织路由
特殊文件 page/layout/error等
组件分类 ui/layout/features
工具库 lib目录统一管理
类型定义 types目录集中管理
命名规范 统一命名风格

下一步,让我们深入学习 Next.js 的路由系统!

最后更新:2026-03-28