Next.js 作为广受欢迎的 Web 开发框架,在第 13 版中引入了全新的 App Router,并将其作为默认路由系统。
在同时参与 Pages Router 与 App Router 项目的过程中,相信不少人也会时常混淆:“这种写法到底是哪一种来着?”我自己就是如此!!
尤其是在基于文件结构的路由定义方式、URL 动态部分的处理方法、渲染机制,以及从 API 获取数据等构成应用开发根基的环节中,Pages Router 与 App Router 存在着不容忽视的差异。
本文聚焦于“容易混淆”的要点,简要整理 Pages Router 与 App Router 的主要差异,作为备忘记录!
文件路由的差异
Pages Router:文件名直接成为路径的简洁设计
放置在 pages 目录下的 .js、.jsx、.ts、.tsx 文件,其文件名会直接作为 URL 路径,这一点十分简洁。
例如:src/page/about.tsx ⇒ 路由为 /about。
App Router:通过文件夹与特定文件名进行结构化
在 App Router 中,只需在 src/app 下放置文件夹或文件,即可实现路由。
但是,用于路由的文件如果命名为特定的文件名 page,则会被视为该路径对应页面的内容。
例如:src/app/about/page.tsx ⇒ 路由为 /about。
App Router 的特色“特殊文件”
此外,在 App Router 中,除了 page.tsx 之外,还有其他具有特定含义的文件。
app/
├── page.tsx --> ページの中身。これが一番よく使うファイル
├── route.tsx --> APIの定義(page.jsと共存不可)
├── layout.tsx --> 共通の見た目
├── loading.tsx --> 読み込み中の画面
├── error.tsx --> エラー時の画面
├── global-error.tsx --> グローバルエラー画面
├── templete.tsx --> 共通の見た目(リセットされるレイアウト)
├── default.tsx --> デフォルトの画面
└── not-found.tsx --> notFound関数がスローされたときの画面
让我特别惊讶的是 not-found.tsx。只要准备好这个页面,访问不存在的 URL 时就会自动显示它。没想到实现起来竟然如此简单,不禁感叹。
渲染方式的差异
在 Pages Router 中,组件基本上以客户端交互为前提,服务端渲染主要用于提升首屏加载速度和 SEO。useState 和 useEffect 等 React Hooks 可以在页面组件及其子组件中自由使用。
另一方面,App Router 中 app/ 目录下的组件默认被视为服务器组件,除非明确指定。因此,useState 和 useEffect 等 React 钩子无法使用。同时,客户端操作(如 onClick)等事件处理程序也无法使用。
因此,在 App Router 中要实现 CSR,需要编写 use client。
use client
这是 Server Component 与 Client Component 的边界声明。声明 use client 后,不仅该组件本身,所有被其导入的模块也都会被视为 Client Component。
动态路由 [○○]
Next.js 的动态路由是一种机制,用于处理URL的一部分会因用户或内容而变化的“动态URL”,例如博客或新闻的文章详情页。
例如,假设有链接跳转到文章 A 和文章 B。
import Link from "next/link";
const linkStyle=`ext-lg font-bold border-b border-primary px-1`
export default function page() {
return (
<div>
<div className="flex gap-6">
<Link href="/post/A" className={linkStyle}>記事Aへのリンク</Link>
<Link href="/post/B" className={linkStyle}>記事Bへのリンク</Link>
</div>
</div>
);
}各链接的跳转目标
使用动态路由时,只需创建[slug]文件,即可让post/A和post/B使用同一模板。
※[ ] 中的命名可自由设定,因此不一定非要使用slug。
App Router 的情况
/app/post/[slug]/page.tsx
export default aysinc function PostPage({ params }: { params: Promise<{ slug: string }>) {
const {slug}= await params;
return (
<div>
<h1>記事: {slug}</h1>
</div>
);
}
使用params。params.slug 中会包含 URL 中的[slug]部分。
※ 过去通过 params 获取参数时无需异步处理,但从 Next.js 15 开始,需改为异步方式。因此,似乎需要将 props 定义为 Promise 类型。
Pages Router 的情况
/pages/post/[slug].tsx
import { useRouter } from 'next/router';
export default function PostPage() {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<h1>記事: {slug}</h1>
</div>
);
}
使用 useRouter。router.query.slug 将获取 URL 中 [slug] 部分的值。
API 数据获取方法
在 Liberogic 的这篇专栏中大显身手的 microCMS,与 Next.js 等框架的兼容性非常好。
App Router 的情况
简单使用 fetch 和 async await 即可。
但是,声明了 use client 的文件会变为 CSR,因此无法使用 async await!
async function getPost() {
const res = await fetch(`https://.../api/data/`);
// エラーハンドリング例
if (!res.ok) {
throw new Error(`Failed to fetch post: ${res.status}`);
}
return res.json();
}
调用方
export default function Example() {
const data = await getPost()
// ... data を使って表示 ...
}
Pages Router 的情况
使用 getStaticProps,并将获取到的数据作为 props 传递给页面组件。
export async function getServerSideProps(context) {
// context.req, context.res などにアクセス可能
const res = await fetch(`https://.../api/data/`, {
headers: { Cookie: context.req.headers.cookie } // 例: Cookieを渡す
});
const data = await res.json();
return {
props: {
data,
},
};
}
调用方
export default function Example({ data }) {
// ... data を使って表示 ...
}
理解两种路由器,熟练掌握 Next.js!
就我而言,最初接触 Next.js 的项目用的是 App Router。
之后在另一个项目中接触了Pages Router,当时就陷入了困惑:“新建页面时,文件名到底是 index.tsx 还是 page.tsx 来着??”
这次的内容只是Next.js的一小部分,但通过重新查阅App Router与Pages Router之间的区别等资料,我自己也梳理清楚了不少内容。
就个人而言,我很喜欢App Router的写法,简单易懂,但我也希望能够在Pages Router的项目中从容应对。
理想的状态是能够更深入地理解 Next.js,并根据项目或作品的需要,灵活选择合适的路由方式!
主要从事标记(Markup)工作,并使用 JavaScript、React 和 Next.js 进行前端开发。看到自己参与的网站顺利上线,我会非常开心!爱好是弹吉他。喜欢猫和烤红薯🐱🍠
Hirappi
前端工程师 / 2022年入职