第15章 文件式路由与布局
Next.js 独特的路由系统和布局管理
作者:zyyc-0336 | 日期:2026-06-12
📋 学习路径建议
- 先学 15.1(基础路由),理解文件路径 = URL 路径
- 再学 15.2(布局),掌握 layout.js 的嵌套机制
- 然后学 15.3(动态路由),学会处理文章详情页
- 最后学 15.4(not-found),处理 404 页面
⚠️ 常见错误提示
⚠️ 路由由文件路径决定,不是由组件名决定
⚠️
layout.js会自动包裹同目录和子目录的所有page.js⚠️ 动态路由的文件夹用
[参数名]命名,参数通过params获取⚠️ 每个目录只能有一个
page.js(但可以有多个组件文件)
15.1 基础路由
Next.js 用文件夹结构来定义路由。
app/ 目录下的每个 page.js 文件都对应一个 URL 路径。
路由规则
| 文件路径 | URL 路径 |
|---|---|
app/page.js | / |
app/about/page.js | /about |
app/posts/page.js | /posts |
app/posts/new/page.js | /posts/new |
// app/page.js → /
export default function Home() {
return <h1>首页</h1>;
}
// app/about/page.js → /about
export default function About() {
return <h1>关于页</h1>;
}
// app/posts/page.js → /posts
export default function Posts() {
return <h1>文章列表</h1>;
}
💡 只有
page.js 会变成可访问的页面。普通的 .js 文件(如 components.js)不会变成路由。15.2 布局 layout.js
layout.js 是一个"壳",包裹同目录和子目录的所有页面。用来放公共的 UI(导航栏、侧边栏、页脚)。
// app/layout.js —— 根布局(所有页面都用这个壳)
export const metadata = {
title: '我的博客',
description: '用 Next.js 搭建的个人博客'
};
export default function RootLayout({ children }) {
return (
<html lang="zh-CN">
<body style={{ margin: 0, fontFamily: 'sans-serif' }}>
{/* 导航栏(公共部分) */}
<nav style={{
display: 'flex',
gap: '20px',
padding: '16px 40px',
backgroundColor: '#2c3e50',
color: 'white'
}}>
<a href="/" style={{ color: 'white', textDecoration: 'none' }}>🏠 首页</a>
<a href="/posts" style={{ color: 'white', textDecoration: 'none' }}>📝 文章</a>
<a href="/about" style={{ color: 'white', textDecoration: 'none' }}>👤 关于</a>
</nav>
{/* children 就是当前页面的内容 */}
<div style={{ padding: '20px 40px' }}>
{children}
</div>
{/* 页脚(公共部分) */}
<footer style={{
padding: '16px 40px',
backgroundColor: '#f5f5f5',
textAlign: 'center',
color: '#999',
marginTop: '40px'
}}>
© 2026 我的博客
</footer>
</body>
</html>
);
}
嵌套布局
子目录也可以有自己的 layout.js。
app/
├── layout.js 根布局(导航栏 + 页脚)
├── page.js 首页(用根布局包裹)
└── posts/
├── layout.js 文章布局(侧边栏,包裹所有文章页)
├── page.js 文章列表
└── [slug]/
└── page.js 文章详情
💡
layout.js 的 children 就是当前要渲染的页面。根布局必须包含 <html> 和 <body> 标签。15.3 动态路由
动态路由:URL 中有一部分是变化的,比如
/posts/javascript-tutorial 和 /posts/css-guide 对应不同的文章。文件夹名用 [参数名] 表示动态部分。
app/posts/[slug]/page.js → /posts/javascript-tutorial
/posts/css-guide
/posts/任何文章名
// app/posts/[slug]/page.js —— 文章详情页
// 模拟文章数据
let posts = {
'javascript-tutorial': {
title: 'JavaScript 入门教程',
content: 'JavaScript 是最流行的编程语言之一...'
},
'css-guide': {
title: 'CSS 布局完全指南',
content: 'Flex 和 Grid 是现代 CSS 布局的两大利器...'
},
'react-intro': {
title: 'React 快速上手',
content: 'React 是 Facebook 开发的前端 UI 库...'
}
};
// params 包含动态路由参数
export default function PostPage({ params }) {
let { slug } = params;
let post = posts[slug];
if (!post) {
return <h1>文章不存在</h1>;
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// app/posts/page.js —— 文章列表(链接到动态路由)
import Link from 'next/link';
let posts = [
{ slug: 'javascript-tutorial', title: 'JavaScript 入门教程' },
{ slug: 'css-guide', title: 'CSS 布局完全指南' },
{ slug: 'react-intro', title: 'React 快速上手' }
];
export default function PostsPage() {
return (
<div>
<h1>📝 文章列表</h1>
{posts.map(post => (
<div key={post.slug} style={{
padding: '12px',
marginBottom: '8px',
border: '1px solid #ddd',
borderRadius: '6px'
}}>
<Link href={`/posts/${post.slug}`} style={{
color: '#3498db',
textDecoration: 'none',
fontSize: '18px'
}}>
{post.title}
</Link>
</div>
))}
</div>
);
}
动态路由速查
| 文件路径 | URL 示例 | params |
|---|---|---|
app/posts/[slug]/page.js | /posts/hello | { slug: 'hello' } |
app/users/[id]/page.js | /users/123 | { id: '123' } |
app/[category]/[id]/page.js | /tech/42 | { category: 'tech', id: '42' } |
15.4 not-found 页面
当用户访问不存在的路由时,显示自定义的 404 页面。
// app/not-found.js —— 全局 404 页面
export default function NotFound() {
return (
<div style={{
textAlign: 'center',
padding: '80px 20px',
fontFamily: 'sans-serif'
}}>
<h1 style={{ fontSize: '72px', color: '#e74c3c' }}>404</h1>
<h2>页面不存在</h2>
<p>你访问的页面可能已被删除或地址错误。</p>
<a href="/" style={{
display: 'inline-block',
marginTop: '20px',
padding: '10px 24px',
backgroundColor: '#3498db',
color: 'white',
textDecoration: 'none',
borderRadius: '6px'
}}>
返回首页
</a>
</div>
);
}
也可以在子目录下创建局部 404 页面:
app/
├── not-found.js 全局 404
└── posts/
└── not-found.js /posts/* 下的 404(优先级更高)
layout.js 的 metadata
// 动态 metadata(每个页面不同)
export async function generateMetadata({ params }) {
return {
title: `文章 - ${params.slug}`
};
}
// 静态 metadata(固定值)
export const metadata = {
title: '我的博客',
description: '个人技术博客'
};
⚠️ 新手易错:
generateMetadata 只能在服务端组件中使用(不能加 "use client")。这是 Next.js 的设计——SEO 元数据在服务端生成。本章复盘——文件式路由与布局核心要点
| 要点 | 说明 |
|---|---|
| 路由规则 | 文件夹路径 = URL 路径,page.js 才是页面 |
| layout.js | 公共外壳,自动包裹同目录和子目录的所有页面 |
| 动态路由 | [slug] 文件夹 + params.slug 获取参数 |
| 嵌套布局 | 子目录可有自己的 layout.js |
| not-found.js | 自定义 404 页面 |
| metadata | 设置页面标题和描述,支持动态生成 |
下一章预告(第16章):我们将学习数据获取与预渲染——SSR/SSG/ISR 三种渲染策略和数据获取方法。
本文档由 zyyc-0336 编写,最后更新:2026-06-14