跳到主要内容

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.tsxlayout.tsx 更适合承担编排职责。重逻辑放进 lib/features/,维护成本会低很多。

3. app/pages/ 混用时没有边界

混用没问题,但最好提前说清楚:

  • 哪些新页面只走 App Router
  • 哪些旧页面暂时留在 Pages Router
  • 哪些公共逻辑需要共享

推荐阅读顺序

  1. App Router 和 Pages Router 怎么看
  2. Next.js 路由系统
  3. 渲染与数据获取
  4. 缓存与重验证

参考资料