Skip to content

Next.js 核心功能详解

1. 全局模板 (_app.js)

问题: 很多页面都有共同的布局,如页头、页脚、导航栏。在每个页面组件中重复编写这些代码非常繁琐。

解决方案: Next.js 提供了 pages/_app.js (或 .jsx) 文件作为全局应用模板。这个组件会包裹所有页面组件,是实现持久化布局、注入全局 CSS、传递共享状态等的理想位置。

实现:

  1. pages 目录下创建一个名为 _app.jsx 的文件。
  2. 创建一个自定义的 App 组件。它会接收两个核心 props:
    • Component: 当前匹配到的页面组件(例如 HomePage, MoviesPage)。
    • pageProps: 传递给该页面组件的 props(在后续数据获取章节中会详细讲解)。
jsx
// pages/_app.jsx
import Header from '../components/Header'; // 假设我们有一个公共头部组件

function MyApp({ Component, pageProps }) {
    // 在这里放置所有页面的通用布局
    return (
        <div>
            <h1>这是我的全局模板</h1>
            <Header />
            <hr />
            
            {/* 渲染当前页面组件,并将 props 传递给它 */}
            <Component {...pageProps} />
        </div>
    );
}

export default MyApp;

效果: 现在,应用中的每一个页面都会被 MyApp 组件包裹,自动拥有了模板中定义的布局和组件。

2. 动态路由

问题: 如何创建 URL 中包含动态参数的页面,例如 /movies/123 用于展示 ID 为 123 的电影详情?

解决方案: Next.js 使用中括号 [] 语法在文件名中定义动态路由段。

2.1. 单个动态参数

  1. 文件结构: pages/movies/[id].jsx
  2. 路由匹配: 这个文件会匹配所有形如 /movies/任意字符串 的路径,例如 /movies/1, /movies/abc

获取参数: 使用 next/router 提供的 useRouter Hook 来访问路由信息。动态参数会存在于 router.query 对象中。

jsx
// pages/movies/[id].jsx
import { useRouter } from 'next/router';

const MovieDetailPage = () => {
    const router = useRouter();
    const { id } = router.query; // 获取文件名 [id] 对应的参数值

    return <h1>电影详情页, ID: {id}</h1>;
};

export default MovieDetailPage;

注意: router.query 同时包含动态路由参数(如 id)和 URL 查询字符串参数(如 ?name=...)。如果两者重名,动态路由参数的优先级更高。

2.2. 捕获所有路由 (Catch-all)

问题: 如何匹配一个路径下的所有子路径,例如 /docs/a/b/c

解决方案: 使用带有三个点的中括号语法 [...param]

  1. 文件结构: pages/docs/[...params].jsx
  2. 路由匹配: 会匹配 /docs/ 后面的所有路径段。
  3. 获取参数: router.query.params 会是一个包含所有匹配段的数组。例如,访问 /docs/a/b/c,则 router.query.params 的值为 ['a', 'b', 'c']

Next.js 使用 <Link> 组件来实现客户端路由(无刷新页面跳转)。

3.1. 基础用法

jsx
// components/Header.jsx
import Link from 'next/link';

export default function Header() {
    return (
        <nav>
            <Link href="/">
                <a>首页</a>
            </Link>
            <Link href="/movies">
                <a>电影页</a>
            </Link>
        </nav>
    );
}

注意: <Link> 组件内部必须包裹一个 <a> 标签或其他可以接收 onClick 事件的元素。

3.2. 动态路由的链接 (重要)

问题: 链接到一个动态路由(如 /movies/3)时,如果直接写 href="/movies/3" 会导致页面刷新,失去客户端导航的优势。

解决方案: 必须同时使用 hrefas 两个属性。

  • href: 指向 pages 目录中对应的文件路径模板
  • as: 指向浏览器地址栏中实际显示的友好 URL
jsx
// 正确的动态路由链接
<Link href="/movies/[id]" as="/movies/3">
    <a>电影详情页 (ID:3)</a>
</Link>

这个语法告诉 Next.js:“这个链接在浏览器中显示为 /movies/3,但请使用 pages/movies/[id].jsx 这个组件来渲染它”。

3.3. 命令式导航 (代码跳转)

使用 useRouter Hook 获取 router 对象,然后调用其 pushreplace 方法。

jsx
import { useRouter } from 'next/router';

function MyComponent() {
    const router = useRouter();

    const handleJump = () => {
        // 跳转到动态路由
        router.push({
            pathname: '/movies/[id]',
            query: { id: 4 },
        });
    };
    
    return <button onClick={handleJump}>跳转到电影详情页 (ID:4)</button>;
}

4. 自定义错误页面

4.1. 404 页面 (Not Found)

  • 实现: 在 pages 目录下创建一个名为 404.jsx 的文件。
  • 效果: 当用户访问一个不存在的路由时,Next.js 会自动渲染这个组件,并正确地返回 404 HTTP 状态码。
jsx
// pages/404.jsx
export default function Custom404() {
    return <h1>404 - 页面未找到</h1>;
}

4.2. 500 页面 (Server Error)

  • 实现: 在 pages 目录下创建一个名为 _error.jsx 的文件。
  • 效果:
    • 生产环境中,当页面渲染时发生服务器端错误,Next.js 会渲染这个组件。
    • 开发环境中,为了便于调试,Next.js 会显示详细的错误堆栈信息,而不是这个自定义页面。
    • 如果 404.js 不存在,_error.js 也会作为 404 页面的降级方案。