Next.js 项目结构
这篇只讲一件事:一个现代 Next.js 项目,目录到底应该怎么理解。
很多人第一次看 Next,会把它当成“React 项目多了一层 文件路由”。这个理解不算错,但不够。到了 App Router 这一代,目录不只是映射 URL,还承担了:
- 路由定义
- 布局组织
- 加载状态
- 错误边界
- 服务端与客户端边界
- 路由处理器
- 元信息文件
所以 Next 的目录结构,本质上已经是运行时结构的一部分。
先看最常见的一套骨架
app/
├── layout.tsx
├── page.tsx
├── loading.tsx
├── error.tsx
├── not-found.tsx
├── blog/
│ ├── page.tsx
│ └── [slug]/
│ └── page.tsx
├── api/
│ └── posts/
│ └── route.ts
└── dashboard/
├── layout.tsx
├── page.tsx
└── settings/
└── page.tsx
components/
lib/
public/
next.config.ts
这套结构里,最重要的不是“文件放在哪”,而是“哪些文件会被 Next 当成特殊入口”。
app/ 是现在的主线
app/ 目录是 App Router 的核心。只要在这里放文件,Next 就会按文件系统 约定把它识别成页面结构的一部分。
page.tsx
对应一个可访问页面。
export default function HomePage() {
return <h1>Home</h1>;
}
layout.tsx
定义共享布局。它会包住当前目录以及子目录下的页面。
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section>
<aside>Sidebar</aside>
<main>{children}</main>
</section>
);
}
loading.tsx
定义该路由段的加载占位。常见于流式渲染和异步数据获取场景。
error.tsx
定义该路由段的错误边界。
not-found.tsx
定义 404 场景的展示结果。
route.ts
定义 Route Handler,也就是基于文件系统的服务端接口。
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ ok: true });
}
pages/ 仍然存在,但定位变了
pages/ 对应的是 Pages Router。它仍然可用,也仍然存在大量存量项目,但已经不是官方继续扩展的主线。
如果项目同时存在 app/ 和 pages/,通常意味着:
- 项目正在迁移
- 有一部分旧页面暂时保留在 Pages Router
- 新功能已经开始走 App Router
这种并存状态完全正常,不需要强行一次性迁完。
components/、lib/、public/ 怎么分
components/
放可复用 UI 组件。
比较稳妥的做法是:
- 页面级文件留在
app/ - 纯 UI 组件放
components/或app/ui/ - 不要把所有东西都塞进页面目录里
lib/
放不直接渲染的逻辑:
- 数据访问函数
- 鉴权 工具
- 缓存标签辅助函数
- 服务端配置
- 第三方 SDK 初始化
public/
放静态资源,例如:
- 图片
- favicon
- robots.txt
- 站点公开可访问的文件
public/logo.png 会对应到 /logo.png。
App Router 里最常见的特殊命名
动态路由
app/blog/[slug]/page.tsx
[slug] 表示动态参数。
路由组
app/(marketing)/about/page.tsx
app/(dashboard)/settings/page.tsx
括号目录不会出现在 URL 里,主要用于整理结构和套不同布局。
并行路由
app/@analytics/page.tsx
app/@revenue/page.tsx
适合复杂后台、仪表盘或需要多块并列渲染的界面。
拦截路由
app/feed/(..)photo/[id]/page.tsx
常见于“列表页点开弹层,但 URL 同步变化”这类交互。
根布局通常长什么样
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>{children}</body>
</html>
);
}
根布局通常负责:
- 全局 HTML 结构
- 全局样式导入
- 字体挂载
- 全站 Provider
- 全站导航或壳层
src/ 能不能用
可以。
官方支持把 app/、pages/、components/ 等目录放进 src/ 下,例如:
src/
app/
components/
lib/
这件事主要是组织偏好问题,不影响 Next 的核心能力。
一个比较实用的组织建议
如果项目规模已经不小,可以按“页面入口”和“业务模块”拆开:
app/
dashboard/
settings/
api/
components/
features/
auth/
billing/
dashboard/
lib/
这种分法的好处是:
app/保持路由清晰- 业务逻辑不会全部淤积在
page.tsx - 跨页面复用更容易管理
最容易出现的结构问题
1. 把所有组件都塞进 app/
短期看起来方便,长期会让页面目录越来越难读。
2. 页面文件里堆太多业务逻辑
page.tsx 和 layout.tsx 更适合承担编排职责。重逻辑放进 lib/ 或 features/,维护成本会低很多。
3. app/ 和 pages/ 混用时没有边界
混用没问题,但最好提前说清楚:
- 哪些新页面只走 App Router
- 哪些旧页面暂时留在 Pages Router
- 哪些公共逻辑需要共享