Next.js Layouts
Next.js App Router 的 layout.tsx 为路由树提供可嵌套、可复用且在导航中保留的共享 UI 外壳,并可与 Route Groups 一起建立产品区域边界。
[!info] related notes
- 所属 MOC:
- 前置概念: nextjs-file-system-routing
- 并列概念:
- 易混淆概念:
- 关系笔记:
Next.js Layouts
一句话定义
layout.tsx 是 Next.js App Router 中某个路由层级的共享 UI 外壳;它包裹该 segment 及其子路由,并通过 children 插入当前子树内容。
RootLayout
└── AppLayout
└── GoalsLayout
└── GoalPage
核心机制
目录:
app/
├── layout.tsx
└── (app)/
├── layout.tsx
├── goals/page.tsx
└── settings/page.tsx
(app)/layout.tsx 可以写成:
export default function AppLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<Sidebar />
<main>{children}</main>
</div>
)
}
访问 /goals 时,children 是 Goals 页面;访问 /settings 时,children 换成 Settings 页面。
Layout 可以嵌套
每一层路由都可以增加自己的 layout.tsx:
app/layout.tsx
↓
(app)/layout.tsx
↓
goals/layout.tsx
↓
[goalId]/page.tsx
因此 App Router 的目录树同时也是一棵 UI Layout Tree。
Root Layout
app/layout.tsx 是最外层 Root Layout,负责应用最外层的 <html> 与 <body>:
export default function RootLayout({ children }) {
return (
<html lang="zh-CN">
<body>{children}</body>
</html>
)
}
全局主题、字体、Provider、全局导航等通常会从这里或其下层布局边界进入。
导航时为什么 Layout 很重要
共享 Layout 不只是“少复制一段 JSX”。App Router 导航时会复用仍然有效的父 Layout,而只替换变化的子路由内容。
AppLayout
├── Sidebar ← 保留
├── Header ← 保留
└── children
├── GoalsPage
└── TasksPage
这意味着放在被保留的 Client Component Layout 中的局部状态,例如:
sidebarCollapsed = true
在 /goals → /tasks 这类共享同一 Layout 的导航中可以继续保留,而不必因为每次换页面都重新挂载外壳。
这类只更新发生变化部分的行为可以用 partial rendering 建立直觉。
Route Group + Layout
Next.js Route Groups 和 Layout 经常一起使用:
app/
├── (auth)/
│ ├── layout.tsx
│ ├── login/page.tsx
│ └── register/page.tsx
└── (app)/
├── layout.tsx
├── goals/page.tsx
└── settings/page.tsx
这样:
(auth)可以使用简单居中的认证布局。(app)可以使用 Sidebar + Topbar 的应用布局。- URL 仍然保持
/login、/goals,不会出现/auth或/app。
所以 Route Group 不只是整理文件夹,也可以帮助定义稳定的 Layout Boundary。
Layout 与普通父组件的区别
普通父组件只是组件树的一部分;App Router Layout 还和路由树、导航复用与 segment 生命周期绑定。
因此:
普通 Parent Component
→ 组件复用
layout.tsx
→ 组件复用 + Route Tree Boundary + 导航时保留
边界与常见误区
Layout 不是权限系统
把页面放进 (app)/layout.tsx 不会自动代表“已登录”。认证和授权仍要显式实现。
Layout 不应该机械无限嵌套
只有确实共享稳定 UI 或需要独立路由边界时才值得增加一层 Layout。目录更深并不代表架构更专业。
同 segment 的 error.tsx 不会兜住该 segment 自己的 Layout 错误
错误边界包裹的是其子路由内容。如果错误发生在某个 layout.tsx 本身,需要更高一层的错误边界处理。见 Next.js Error Boundaries。
最短记忆方式
layout.tsx
= shared route shell
+ nested UI boundary
+ preserved parent during navigation
它主要回答:这一批 Route 之间,什么 UI 应该稳定存在并共享?