前言

在 Next.js 应用中,路由扮演着至关重要的角色。它决定了页面的渲染方式以及请求的处理逻辑。
Next.js 提供了两套路由解决方案:Pages Router 与 App Router。自 v13.4 版本起,App Router 成为默认的路由系统,为新的 Next.js 项目带来更多优势。

文件系统路由

Next.js 的路由系统基于文件系统,使得每个文件能够直接对应一个路由地址:

  • pages/index.js 对应根路由 /
  • pages/about.js 对应路由 /about

从 Pages Router 迁移到 App Router

自 Next.js v13 起,App Router 成为新的默认路由模式。与 Pages Router 相比,App Router 提供了更强大的功能、更好的性能和更灵活的代码组织方式。

  • Pages Router 结构:
pages/
├── index.js
├── about.js
└── more.js
  • App Router 结构:
src/
└── app/
    ├── page.js
    ├── layout.js
    ├── template.js
    ├── loading.js
    ├── error.js
    └── not-found.js
    ├── about/
    │   └── page.js
    └── more/
        └── page.js

App Router 通过引入诸如 layout.js、template.js 等特殊文件,实现了更细致的页面结构划分,从而改善了代码的组织和管理。
尽管推荐使用 App Router,但 Next.js 仍然兼容 Pages Router。如果你想继续使用 Pages Router,可以在项目的 src 目录或根目录创建 pages 文件夹。
需要注意的是:

  • App Router 与 Pages Router 可以共存,但 App Router 优先级更高。
  • 如果两者解析到同一个 URL,可能会导致构建错误。

使用 App Router

App Router 是 Next.js 基于文件系统的路由解决方案,它通过目录和文件的方式定义路由、页面、布局、模板等,使得项目结构清晰,便于管理。

定义路由(Routes)

App Router 通过文件夹来定义路由。每个文件夹代表一个 URL 片段,创建嵌套文件夹即可创建嵌套路由。
例如,app/dashboard/settings 目录对应的路由地址是 /dashboard/settings。

定义页面(Pages)

为了让路由可访问,你需要创建名为 page.js 的文件。这是一种约定,例如:

  • app/page.js => 路由 /
  • app/dashboard/page.js => 路由 /dashboard
  • app/dashboard/settings/page.js => 路由 /dashboard/settings

页面通常用于展示 UI,如下面的代码示例:

// app/page.js
export default function Page() {
  return <h1>Hello, Next.js!</h1>;
}

访问 http://localhost:3000/ 会显示 “Hello, Next.js!”(把项目原来的 page.jsx 和它的样式删除)。

Next.js 路由全面解析:从 Pages Router 迁移到 App Router

定义布局(Layouts)

布局是多个页面共享的 UI 结构,如侧边导航栏。
布局文件通常命名为 layout.js,默认导出一个接收 children prop 的 React 组件。例如:

// app/dashboard/layout.js 定义了导航栏
export default function DashboardLayout({ children }) {
  return (
    <section>
      <nav>Navigation</nav>
      {children}
    </section>
  );
}
// app/dashboard/page.js 定义了页面内容
export default function Page() {
	return <h1>Hello, Dashboard!</h1>;
}

Next.js 路由全面解析:从 Pages Router 迁移到 App Router

定义模板(Templates)

模板类似于布局,也会传入每个子布局或页面。
但模板在路由切换时会为每个 children 创建一个新实例,不保持状态,
定义模板,创建 template.js 文件,如下例:

// app/template.js
export default function Template({ children }) {
  return <div>{children}</div>;
}

定义加载界面(Loading UI)

Next.js 提供了 loading.js 文件,用于展示加载界面。
这个功能是通过 React 的 Suspense API 实现的。当路由变化时,会立即展示 fallback UI,等数据加载完成后,再展示实际内容。

// app/dashboard/loading.js
export default function DashboardLoading() {
	return <>Loading dashboard...</>;
}

同级的 page.js 代码如下:

// app/dashboard/page.js
async function getData() {
	await new Promise(resolve => setTimeout(resolve, 2000));
	return {
		message: 'Hello, Dashboard!',
	};
}
export default async function DashboardPage(props) {
	const { message } = await getData();
	return <h1>{message}</h1>;
}

请求数据先 loading:

Next.js 路由全面解析:从 Pages Router 迁移到 App Router

请求完成后:
Next.js 路由全面解析:从 Pages Router 迁移到 App Router

定义错误处理(Error Handling)

Next.js 提供了 error.js 文件,用于创建发生错误时的展示 UI。
这个功能是通过 React 的 Error Boundary 功能实现的。

// app/dashboard/error.js
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

定义 404 页面

not-found.js 用于定义资源不存在时显示的页面。
在 app 目录下新建一个 not-found.js,即可自定义 404 页面效果。
这个文件只能由两种情况触发:当组件抛出了 notFound 函数时,或者当路由地址不匹配时。

// app/not-found.js
import Link from 'next/link';
export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <Link href="/">Return Home</Link>
    </div>
  );
}