第15章 文件式路由与布局

Next.js 独特的路由系统和布局管理

作者:zyyc-0336  |  日期:2026-06-12

📋 学习路径建议
  1. 先学 15.1(基础路由),理解文件路径 = URL 路径
  2. 再学 15.2(布局),掌握 layout.js 的嵌套机制
  3. 然后学 15.3(动态路由),学会处理文章详情页
  4. 最后学 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.jschildren 就是当前要渲染的页面。根布局必须包含 <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