Next.js 面试题详细答案 - Q15
Q15: 如何实现路由组(Route Groups)?(folder) 的语法有什么作用?(组织路由而不影响 URL 路径)
路由组(Route Groups)概述
路由组使用 (folder) 语法来组织路由,不会影响 URL 路径。它们主要用于代码组织和布局管理。
基本语法
1. 简单路由组
// 文件结构
app/
├── layout.js
├── page.js
├── (marketing)/
│ ├── about/
│ │ └── page.js
│ ├── contact/
│ │ └── page.js
│ └── pricing/
│ └── page.js
└── (dashboard)/
├── profile/
│ └── page.js
├── settings/
│ └── page.js
└── analytics/
└── page.js
// URL 路径
/ // app/page.js
/about // app/(marketing)/about/page.js
/contact // app/(marketing)/contact/page.js
/pricing // app/(marketing)/pricing/page.js
/profile // app/(dashboard)/profile/page.js
/settings // app/(dashboard)/settings/page.js
/analytics // app/(dashboard)/analytics/page.js
2. 路由组布局
// app/(marketing)/layout.js - 营销页面布局
export default function MarketingLayout({ children }) {
return (
<div className="marketing-layout">
<header>
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
<a href="/contact">联系</a>
<a href="/pricing">价格</a>
</nav>
</header>
<main>{children}</main>
<footer>
<p>© 2024 公司名称</p>
</footer>
</div>
)
}
// app/(dashboard)/layout.js - 仪表盘布局
export default function DashboardLayout({ children }) {
return (
<div className="dashboard-layout">
<aside className="sidebar">
<nav>
<a href="/profile">个人资料</a>
<a href="/settings">设置</a>
<a href="/analytics">分析</a>
</nav>
</aside>
<main className="content">{children}</main>
</div>
)
}
实际应用示例
1. 多租户应用
// 文件结构
app/
├── layout.js
├── page.js
├── (public)/
│ ├── layout.js
│ ├── login/
│ │ └── page.js
│ ├── register/
│ │ └── page.js
│ └── forgot-password/
│ └── page.js
├── (app)/
│ ├── layout.js
│ ├── dashboard/
│ │ └── page.js
│ ├── profile/
│ │ └── page.js
│ └── settings/
│ └── page.js
└── (admin)/
├── layout.js
├── users/
│ └── page.js
├── analytics/
│ └── page.js
└── settings/
└── page.js
// app/(public)/layout.js - 公共页面布局
export default function PublicLayout({ children }) {
return (
<div className="public-layout">
<header>
<h1>我的应用</h1>
<nav>
<a href="/login">登录</a>
<a href="/register">注册</a>
</nav>
</header>
<main>{children}</main>
</div>
)
}
// app/(app)/layout.js - 应用页面布局
export default function AppLayout({ children }) {
return (
<div className="app-layout">
<header>
<h1>我的应用</h1>
<nav>
<a href="/dashboard">仪表盘</a>
<a href="/profile">个人资料</a>
<a href="/settings">设置</a>
</nav>
</header>
<main>{children}</main>
</div>
)
}
// app/(admin)/layout.js - 管理页面布局
export default function AdminLayout({ children }) {
return (
<div className="admin-layout">
<header>
<h1>管理后台</h1>
<nav>
<a href="/users">用户管理</a>
<a href="/analytics">数据分析</a>
<a href="/settings">系统设置</a>
</nav>
</header>
<main>{children}</main>
</div>
)
}
2. 多语言应用
// 文件结构
app/
├── layout.js
├── page.js
├── (en)/
│ ├── layout.js
│ ├── about/
│ │ └── page.js
│ ├── contact/
│ │ └── page.js
│ └── blog/
│ └── page.js
├── (zh)/
│ ├── layout.js
│ ├── about/
│ │ └── page.js
│ ├── contact/
│ │ └── page.js
│ └── blog/
│ └── page.js
└── (ja)/
├── layout.js
├── about/
│ └── page.js
├── contact/
│ └── page.js
└── blog/
└── page.js
// app/(en)/layout.js - 英文布局
export default function EnglishLayout({ children }) {
return (
<div className="english-layout">
<header>
<nav>
<a href="/about">About</a>
<a href="/contact">Contact</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>{children}</main>
</div>
)
}
// app/(zh)/layout.js - 中文布局
export default function ChineseLayout({ children }) {
return (
<div className="chinese-layout">
<header>
<nav>
<a href="/about">关于我们</a>
<a href="/contact">联系我们</a>
<a href="/blog">博客</a>
</nav>
</header>
<main>{children}</main>
</div>
)
}
3. 设备特定布局
// 文件结构
app/
├── layout.js
├── page.js
├── (mobile)/
│ ├── layout.js
│ ├── dashboard/
│ │ └── page.js
│ └── profile/
│ └── page.js
├── (desktop)/
│ ├── layout.js
│ ├── dashboard/
│ │ └── page.js
│ └── profile/
│ └── page.js
└── (tablet)/
├── layout.js
├── dashboard/
│ └── page.js
└── profile/
└── page.js
// app/(mobile)/layout.js - 移动端布局
export default function MobileLayout({ children }) {
return (
<div className="mobile-layout">
<header className="mobile-header">
<button className="menu-btn">☰</button>
<h1>我的应用</h1>
</header>
<main className="mobile-content">{children}</main>
<nav className="mobile-nav">
<a href="/dashboard">仪表盘</a>
<a href="/profile">个人资料</a>
</nav>
</div>
)
}
// app/(desktop)/layout.js - 桌面端布局
export default function DesktopLayout({ children }) {
return (
<div className="desktop-layout">
<aside className="sidebar">
<nav>
<a href="/dashboard">仪表盘</a>
<a href="/profile">个人资料</a>
</nav>
</aside>
<main className="content">{children}</main>
</div>
)
}
高级用法
1. 嵌套路由组
// 文件结构
app/
├── layout.js
├── page.js
├── (marketing)/
│ ├── layout.js
│ ├── about/
│ │ └── page.js
│ └── (blog)/
│ ├── layout.js
│ ├── page.js
│ └── [slug]/
│ └── page.js
└── (app)/
├── layout.js
├── dashboard/
│ └── page.js
└── (admin)/
├── layout.js
├── users/
│ └── page.js
└── settings/
└── page.js
// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
return (
<div className="marketing-layout">
<header>营销页面头部</header>
<main>{children}</main>
<footer>营销页面底部</footer>
</div>
)
}
// app/(marketing)/(blog)/layout.js
export default function BlogLayout({ children }) {
return (
<div className="blog-layout">
<aside>博客侧边栏</aside>
<main>{children}</main>
</div>
)
}
2. 条件布局
// app/(auth)/layout.js
export default function AuthLayout({ children }) {
return (
<div className="auth-layout">
<div className="auth-container">
<div className="auth-header">
<h1>欢迎回来</h1>
</div>
<main>{children}</main>
<div className="auth-footer">
<p>需要帮助?联系客服</p>
</div>
</div>
</div>
)
}
// app/(dashboard)/layout.js
export default function DashboardLayout({ children }) {
return (
<div className="dashboard-layout">
<aside className="sidebar">
<div className="sidebar-header">
<h2>仪表盘</h2>
</div>
<nav className="sidebar-nav">
<a href="/dashboard">概览</a>
<a href="/dashboard/analytics">分析</a>
<a href="/dashboard/settings">设置</a>
</nav>
</aside>
<main className="main-content">{children}</main>
</div>
)
}
3. 动态路由组
// 文件结构
app/
├── layout.js
├── page.js
├── (tenant)/
│ ├── layout.js
│ ├── dashboard/
│ │ └── page.js
│ └── [tenantId]/
│ ├── layout.js
│ ├── settings/
│ │ └── page.js
│ └── users/
│ └── page.js
└── (public)/
├── layout.js
├── login/
│ └── page.js
└── register/
└── page.js
// app/(tenant)/layout.js
export default function TenantLayout({ children }) {
return (
<div className="tenant-layout">
<header>租户页面头部</header>
<main>{children}</main>
</div>
)
}
// app/(tenant)/[tenantId]/layout.js
export default function TenantSpecificLayout({ children, params }) {
return (
<div className="tenant-specific-layout">
<header>
<h1>租户 {params.tenantId}</h1>
</header>
<main>{children}</main>
</div>
)
}
最佳实践
1. 合理组织路由组
// 按功能组织
app/
├── (marketing)/ // 营销页面
├── (app)/ // 应用页面
├── (admin)/ // 管理页面
└── (auth)/ // 认证页面
// 按用户类型组织
app/
├── (public)/ // 公共页面
├── (user)/ // 用户页面
├── (admin)/ // 管理员页面
└── (moderator)/ // 版主页面
2. 布局继承
// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
return (
<div className="marketing-layout">
<header>营销头部</header>
<main>{children}</main>
<footer>营销底部</footer>
</div>
)
}
// app/(marketing)/(blog)/layout.js
export default function BlogLayout({ children }) {
return (
<div className="blog-layout">
<aside>博客侧边栏</aside>
<main>{children}</main>
</div>
)
}
3. 条件渲染
// app/(admin)/layout.js
export default function AdminLayout({ children }) {
return (
<div className="admin-layout">
<header>
<h1>管理后台</h1>
<nav>
<a href="/admin/users">用户管理</a>
<a href="/admin/settings">系统设置</a>
</nav>
</header>
<main>{children}</main>
</div>
)
}
总结
路由组的主要作用:
- 代码组织:将相关的路由组织在一起,提高代码可维护性
- 布局管理:为不同的路由组提供不同的布局
- URL 路径:不影响实际的 URL 路径,保持 URL 的简洁性
- 条件渲染:可以根据不同的条件渲染不同的布局
- 嵌套支持:支持嵌套的路由组,提供更灵活的组织方式
路由组是 Next.js 13+ 中组织复杂应用结构的重要工具,特别适合大型应用和需要不同布局的页面。