Q15: 如何实现路由组(Route Groups)?(folder) 的语法有什么作用?(组织路由而不影响 URL 路径)

34 阅读3分钟

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>
  )
}

总结

路由组的主要作用:

  1. 代码组织:将相关的路由组织在一起,提高代码可维护性
  2. 布局管理:为不同的路由组提供不同的布局
  3. URL 路径:不影响实际的 URL 路径,保持 URL 的简洁性
  4. 条件渲染:可以根据不同的条件渲染不同的布局
  5. 嵌套支持:支持嵌套的路由组,提供更灵活的组织方式

路由组是 Next.js 13+ 中组织复杂应用结构的重要工具,特别适合大型应用和需要不同布局的页面。