主题
Next.js 路由组:优雅的组织代码
更新: 11/26/2025字数: 0 字 时长: 0 分钟
什么是路由组?
路由组允许你将相关的路由组织在一起,不会影响实际的 URL 路径结构。通过将文件夹用括号括起来 (folder_name),你可以创建逻辑分组,让项目结构更清晰,同时保持 URL 的简洁性。
核心特性速览
| 特性 | 说明 | 示例 |
|---|---|---|
| 不影响 URL | 分组不会出现在浏览器地址栏 | (auth)/login → /login |
| 逻辑组织 | 相关路由可以放在一起 | (shop)/products, (shop)/cart |
| 共享布局 | 组内路由可以共享特定布局 | (admin) 组使用管理后台布局 |
| 灵活嵌套 | 支持多层级分组 | (platform)/(web)/dashboard |
基础用法:快速入门
项目结构示例
app/
├── (auth)/
│ ├── login/
│ │ └── page.js
│ ├── register/
│ │ └── page.js
│ └── layout.js
├── (shop)/
│ ├── products/
│ │ └── page.js
│ ├── cart/
│ │ └── page.js
│ └── layout.js
├── layout.js
└── page.js1. 认证路由组
jsx
// app/(auth)/layout.js - 认证相关布局
export default function AuthLayout({ children }) {
return (
<div className="auth-layout">
<div className="auth-container">
<div className="auth-background">
<h1>欢迎使用我们的服务</h1>
</div>
<div className="auth-form-area">{children}</div>
</div>
</div>
);
}jsx
// app/(auth)/login/page.js
export default function LoginPage() {
return (
<div className="auth-form">
<h2>用户登录</h2>
<form>
<input type="email" placeholder="邮箱地址" />
<input type="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
<p>
还没有账号? <a href="/register">立即注册</a>
</p>
</div>
);
}访问路径: /login (注意:URL 中没有 (auth))
2. 电商路由组
jsx
// app/(shop)/layout.js - 电商相关布局
export default function ShopLayout({ children }) {
return (
<div className="shop-layout">
<header className="shop-header">
<nav>
<a href="/products">商品</a>
<a href="/cart">购物车</a>
</nav>
</header>
<main className="shop-main">{children}</main>
</div>
);
}高级应用场景
场景 1:多布局系统
对于复杂的应用,你可能需要多种不同的布局:
app/
├── (marketing)/
│ ├── layout.js # 营销页面布局
│ ├── page.js # 首页 → /
│ ├── about/
│ │ └── page.js # 关于我们 → /about
│ └── pricing/
│ └── page.js # 价格页面 → /pricing
├── (app)/
│ ├── layout.js # 应用内布局
│ ├── dashboard/
│ │ └── page.js # 仪表盘 → /dashboard
│ └── settings/
│ └── page.js # 设置 → /settings
└── layout.js # 根布局jsx
// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
return (
<div className="marketing-layout">
<header className="marketing-header">
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
<a href="/pricing">价格</a>
<a href="/dashboard">进入应用</a>
</nav>
</header>
{children}
<footer className="marketing-footer">
<p>© 2024 公司名称</p>
</footer>
</div>
);
}jsx
// app/(app)/layout.js
export default function AppLayout({ children }) {
return (
<div className="app-layout">
<aside className="sidebar">
<nav>
<a href="/dashboard">仪表盘</a>
<a href="/settings">设置</a>
<a href="/">返回官网</a>
</nav>
</aside>
<main className="app-main">{children}</main>
</div>
);
}场景 2:多租户 SaaS 应用
app/
├── (saas)/
│ ├── (admin)/
│ │ ├── layout.js
│ │ ├── users/
│ │ │ └── page.js # /users
│ │ └── analytics/
│ │ └── page.js # /analytics
│ ├── (user)/
│ │ ├── layout.js
│ │ ├── profile/
│ │ │ └── page.js # /profile
│ │ └── projects/
│ │ └── page.js # /projects
│ └── layout.js
└── page.jsjsx
// app/(saas)/layout.js - 根 SaaS 布局
export default function SaaSLayout({ children }) {
return (
<div className="saas-layout">
<header className="saas-header">
<h1>企业 SaaS 平台</h1>
<div className="user-menu">用户菜单</div>
</header>
{children}
</div>
);
}jsx
// app/(saas)/(admin)/layout.js
export default function AdminLayout({ children }) {
const user = getCurrentUser();
if (!user?.isAdmin) {
redirect("/unauthorized");
}
return (
<div className="admin-layout">
<aside className="admin-sidebar">
<h3>管理员面板</h3>
<nav>
<a href="/users">用户管理</a>
<a href="/analytics">数据分析</a>
</nav>
</aside>
<div className="admin-content">{children}</div>
</div>
);
}场景 3:A/B 测试和特性开关
app/
├── (experiments)/
│ ├── (feature-a)/
│ │ └── dashboard/
│ │ └── page.js # 新版本仪表盘
│ ├── (feature-b)/
│ │ └── dashboard/
│ │ └── page.js # 另一个版本
│ └── dashboard/
│ └── page.js # 默认版本jsx
// 中间件:根据用户分组路由
export function middleware(request) {
const userId = getUserId(request);
const userGroup = getUserExperimentGroup(userId);
if (request.nextUrl.pathname === "/dashboard") {
if (userGroup === "feature-a") {
return NextResponse.rewrite(new URL("/(experiments)/(feature-a)/dashboard", request.url));
} else if (userGroup === "feature-b") {
return NextResponse.rewrite(new URL("/(experiments)/(feature-b)/dashboard", request.url));
}
}
return NextResponse.next();
}路由组与平行路由的结合
路由组和平行路由可以强强联合,创建极其灵活的架构:
app/
├── (app)/
│ ├── @sidebar/
│ │ ├── default.js
│ │ └── navigation.js
│ ├── @header/
│ │ └── userinfo.js
│ ├── layout.js
│ └── dashboard/
│ └── page.jsjsx
// app/(app)/layout.js
export default function AppLayout({ children, sidebar, header }) {
return (
<div className="app-layout">
<aside className="app-sidebar">{sidebar}</aside>
<div className="app-main">
<header className="app-header">{header}</header>
<main className="app-content">{children}</main>
</div>
</div>
);
}最佳实践和技巧
1. 命名约定
bash
# 好的命名
(app)/
(marketing)/
(auth)/
(api)/
# 避免的命名
[group]/
{group}/
-group-/2. 避免过度嵌套
bash
# 推荐 - 清晰的扁平结构
app/
├── (auth)/
├── (app)/
├── (marketing)/
└── (api)/
# 避免 - 过度嵌套
app/
├── (platform)/
│ ├── (web)/
│ │ ├── (app)/
│ │ └── (marketing)/
│ └── (mobile)/3. 共享组件和工具
jsx
// 在组内共享组件
app/
├── (ecommerce)/
│ ├── components/
│ │ ├── ProductCard.js
│ │ └── ShoppingCart.js
│ ├── hooks/
│ │ └── useCart.js
│ ├── products/
│ └── cart/4. 组特定的中间件
javascript
// middleware.js
export function middleware(request) {
// 认证组的路由保护
if (request.nextUrl.pathname.startsWith("/login") || request.nextUrl.pathname.startsWith("/register")) {
return handleAuthRoutes(request);
}
// 管理组的路由保护
if (request.nextUrl.pathname.startsWith("/admin")) {
return handleAdminRoutes(request);
}
}常见问题解答
Q: 路由组会影响 SEO 吗?
A: 不会。路由组只是开发时的组织工具,不会影响最终的 URL 结构,因此对 SEO 完全没有影响。
Q: 可以在路由组中使用动态路由吗?
A: 当然可以!
app/
├── (shop)/
│ ├── products/
│ │ └── [id]/
│ │ └── page.js # /products/123
│ └── categories/
│ └── [slug]/
│ └── page.js # /categories/electronicsQ: 路由组支持 catch-all 路由吗?
A: 支持!
app/
├── (docs)/
│ └── [...slug]/
│ └── page.js # 匹配 /docs/a/b/c 等Q: 如何在不同组之间共享布局?
A: 使用根布局或在组外定义共享组件:
jsx
// app/layout.js - 根布局共享给所有组
export default function RootLayout({ children }) {
return (
<html lang="zh">
<body className="global-styles">{children}</body>
</html>
);
}