上节我们搭建好了 Next.js 开发环境,本节就来详细了解一下 Next.js 的项目结构。很多初学者刚打开项目时会看到一堆文件和文件夹,不知道每个都是干什么的。别担心,本节会带你理清这些目录和文件的用途,让你对项目结构有一个清晰的认识。
项目结构概览
my-nextjs-app/
├── app/ # App Router(主要工作目录)
│ ├── (auth)/ # 路由组(不影响 URL)
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── register/
│ │ └── page.tsx
│ ├── api/ # API 路由
│ │ └── users/
│ │ └── route.ts
│ ├── blog/ # 应用路由
│ │ ├── [slug]/ # 动态路由
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── layout.tsx # 根布局
│ ├── page.tsx # 首页
│ ├── loading.tsx # 加载状态
│ ├── error.tsx # 错误处理
│ ├── not-found.tsx # 404 页面
│ └── globals.css # 全局样式
├── components/ # 共享组件
│ ├── ui/ # UI 基础组件
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Card.tsx
│ └── layout/ # 布局组件
│ ├── Header.tsx
│ └── Footer.tsx
├── lib/ # 工具函数
│ ├── utils.ts
│ ├── api.ts
│ └── constants.ts
├── hooks/ # 自定义 Hooks
│ ├── useAuth.ts
│ └── useData.ts
├── types/ # TypeScript 类型
│ └── index.ts
├── public/ # 静态资源
│ ├── images/
│ ├── fonts/
│ └── favicon.ico
├── styles/ # 样式文件(可选)
│ └── globals.css
├── .env.local # 环境变量
├── .eslintrc.json # ESLint 配置
├── .gitignore # Git 忽略文件
├── next.config.js # Next.js 配置
├── package.json # 项目配置
├── tsconfig.json # TypeScript 配置
└── README.md # 项目说明
核心目录详解
1. app/ 目录 - App Router
app/ 目录是 Next.js 13+ 推荐的新路由系统,基于 React Server Components 构建。这是你工作中最常打交道的目录,绝大部分页面和路由都会放在这里。
特殊文件
| 文件 | 用途 | 必需 |
|---|---|---|
layout.tsx | 定义布局和 UI | 可选 |
page.tsx | 定义路由的独特 UI | 必需(可访问路由) |
loading.tsx | 加载时的 UI 替换 | 可选 |
error.tsx | 错误边界 UI | 可选 |
not-found.tsx | 404 页面 | 可选 |
route.ts | API 端点 | API 路由必需 |
示例结构
app/
├── (marketing)/ # 路由组
│ ├── about/
│ │ └── page.tsx # /about
│ ├── layout.tsx # 营销页面共享布局
│ └── page.tsx # /
├── (shop)/ # 另一个路由组
│ ├── account/
│ │ └── page.tsx # /account
│ └── layout.tsx # 商店页面共享布局
├── products/
│ ├── [id]/ # 动态段
│ │ └── page.tsx # /products/123
│ └── page.tsx # /products
├── api/
│ └── users/
│ └── route.ts # /api/users (API)
├── layout.tsx # 根布局(所有页面共享)
└── page.tsx # 首页
2. components/ 目录
这里存放可复用的 React 组件。当你发现一段 UI 代码在多个页面重复出现时,就可以把它抽取成一个组件放到这里。随着项目变大,良好的组件组织会让代码更容易维护。
components/
├── ui/ # 基础 UI 组件
│ ├── Button.tsx
│ ├── Input.tsx
│ ├── Modal.tsx
│ └── Table.tsx
├── layout/ # 布局组件
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── Sidebar.tsx
│ └── Navigation.tsx
├── features/ # 功能组件
│ ├── UserProfile.tsx
│ ├── ProductCard.tsx
│ └── CommentList.tsx
└── forms/ # 表单组件
├── LoginForm.tsx
└── ContactForm.tsx
组件示例
// components/ui/Button.tsx
interface ButtonProps {
children: React.ReactNode
onClick?: () => void
variant?: 'primary' | 'secondary'
}
export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
return (
<button
onClick={onClick}
className={`px-4 py-2 rounded ${
variant === 'primary'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-800'
}`}
>
{children}
</button>
)
}
3. lib/ 目录
这里存放工具函数、API 客户端、常量等辅助代码。把不属于任何特定业务逻辑的通用代码放在这里是个好习惯。
lib/
├── utils/ # 工具函数
│ ├── format.ts # 格式化函数
│ ├── validation.ts # 验证函数
│ └── helpers.ts # 辅助函数
├── api/ # API 客户端
│ ├── client.ts
│ ├── users.ts
│ └── products.ts
├── db/ # 数据库相关
│ ├── connect.ts
│ └── queries.ts
└── constants.ts # 常量定义
工具函数示例
// lib/utils/format.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date)
}
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY',
}).format(amount)
}
4. hooks/ 目录
存放自定义 React Hooks。如果你有一些状态逻辑需要在多个组件中复用,就可以封装成自定义 Hook 放在这里。
hooks/
├── useAuth.ts # 认证相关
├── useData.ts # 数据获取
├── useForm.ts # 表单处理
└── useLocalStorage.ts # 本地存储
Hook 示例
// hooks/useAuth.ts
'use client'
import { useState, useEffect } from 'react'
export function useAuth() {
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [user, setUser] = useState(null)
useEffect(() => {
// 检查认证状态
const token = localStorage.getItem('token')
setIsAuthenticated(!!token)
}, [])
return { isAuthenticated, user }
}
5. public/ 目录
这里存放静态资源,比如图片、字体、favicon 等。放在 public 目录下的文件可以直接通过 URL 访问,不需要 import。
public/
├── images/
│ ├── logo.png
│ └── banner.jpg
├── fonts/
│ └── custom-font.woff2
├── favicon.ico
└── robots.txt
使用方式
// 在组件中引用
<Image src="/images/logo.png" alt="Logo" width={200} height={100} />
6. 根配置文件
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// 图片优化配置
images: {
domains: ['example.com'],
formats: ['image/avif', 'image/webp'],
},
// 环境变量
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
// 重定向
async redirects() {
return [
{
source: '/old-path',
destination: '/new-path',
permanent: true,
},
]
},
}
module.exports = nextConfig
tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
路由组织最佳实践
1. 使用路由组
路由组 (group-name) 是 Next.js 的一个很有用的特性,它不会影响 URL 路径,但可以帮助你更好地组织代码和共享布局。比如你想把某些页面放在一起管理,但又不想改变 URL 结构,就可以用路由组。
app/
├── (marketing)/ # /about, /contact
│ ├── about/
│ │ └── page.tsx
│ ├── contact/
│ │ └── page.tsx
│ └── layout.tsx # 营销页面共享布局
├── (dashboard)/ # /dashboard, /settings
│ ├── dashboard/
│ │ └── page.tsx
│ ├── settings/
│ │ └── page.tsx
│ └── layout.tsx # 需要认证的布局
└── page.tsx # 首页
2. 动态路由
动态路由在实际开发中非常常见,比如博客文章页、用户详情页等。使用方括号 [param] 就可以创建动态路由段,Next.js 会自动匹配并解析参数。
app/
├── blog/
│ ├── [slug]/ # /blog/hello-world
│ │ └── page.tsx
│ └── page.tsx # /blog
├── products/
│ ├── [category]/ # /products/electronics
│ │ └── page.tsx
│ └── page.tsx # /products
└── users/
└── [id]/ # /users/123
├── [action]/ # /users/123/edit
│ └── page.tsx
└── page.tsx # /users/123
3. 并行和拦截路由
并行路由和拦截路由是 Next.js 的高级特性,可以实现一些复杂的交互效果,比如模态框、并行加载多个页面等。这些特性在实际项目中非常有用,但理解起来可能需要一点时间。
app/
├── @dashboard/ # 并行路由槽
│ └── page.tsx
├── (.)modal/ # 拦截路由
│ └── photo/[id]/page.tsx
├── dashboard/
│ └── page.tsx
└── layout.tsx
文件命名约定
路由相关
| 模式 | 说明 | 示例 URL |
|---|---|---|
folder/page.tsx | 标准路由 | /folder |
folder/[slug]/page.tsx | 动态路由 | /folder/value |
folder/[[...slug]]/page.tsx | 捕获所有路由 | /folder/a/b/c |
(group)/page.tsx | 路由组 | /page |
folder/(.)modal/... | 拦截路由 | - |
特殊文件
| 文件 | 说明 |
|---|---|
_filename.tsx | 私有文件,不创建路由 |
filename.server.tsx | 仅在服务器运行 |
filename.client.tsx | 仅在客户端运行 |
代码组织建议
1. 按功能组织
按功能组织是一种常见的项目结构方式,把相关的功能放在一起。这种方式适合中小型项目,代码结构清晰易懂。
app/
├── (auth)/
│ ├── login/
│ ├── register/
│ └── forgot-password/
├── (dashboard)/
│ ├── overview/
│ ├── analytics/
│ └── settings/
└── (public)/
├── about/
├── contact/
└── pricing/
2. 按层级组织
按层级组织适合大型项目,比如有 API 版本管理、多级管理后台等场景。这种方式可以让结构更有层次感。
app/
├── api/
│ ├── v1/
│ │ ├── users/
│ │ └── posts/
│ └── v2/
│ └── users/
└── admin/
└── users/
├── [id]/
└── new/
3. 组件分层
组件分层是一种借鉴原子设计的组织方式,把组件按照复杂度分成原子、分子、组织、模板等层级。这种方式适合 UI 组件库或者设计系统比较完善的项目。
components/
├── atoms/ # 最小单元
│ ├── Button.tsx
│ └── Input.tsx
├── molecules/ # 组合原子
│ ├── SearchBar.tsx
│ └── FormField.tsx
├── organisms/ # 复杂组件
│ ├── Header.tsx
│ └── ProductCard.tsx
└── templates/ # 页面模板
└── BlogLayout.tsx
环境变量
环境变量用来存储一些敏感信息或者配置,比如数据库连接字符串、API 密钥等。创建 .env.local 文件来存放这些信息,记得把这个文件加到 .gitignore 里,不要提交到代码仓库。
# 数据库
DATABASE_URL=postgresql://...
# API 密钥
API_KEY=your_api_key
API_SECRET=your_api_secret
# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000
访问方式:
// 服务器端
const dbUrl = process.env.DATABASE_URL
// 客户端(必须以 NEXT_PUBLIC_ 开头)
const appUrl = process.env.NEXT_PUBLIC_APP_URL
总结
本节我们详细了解了 Next.js 的项目结构,包括各个目录的用途、路由组织方式、以及一些最佳实践。掌握项目结构是学好 Next.js 的基础,建议你多花点时间理解这些内容。
如果你对本节内容有任何疑问,欢迎在评论区提出来,我们一起学习讨论。