Next.js的App router和Pages router的区别你知道多少?

以下是Next.js 中 App Router 和 Pages Router 的区别,并提供相应的代码示例方便大家理解记忆。

App Router 是 Next.js 演进过程中的一个重要里程碑。Pages Router 是 Next.js 长期以来的核心路由机制,稳定且功能强大。而从 Next.js 13 开始引入的 App Router ,则被视为下一代路由方案,它基于 React Server Components 构建,带来了许多革新性的特性。

1、核心区别概览

特性 App Router (Next.js 13+) Pages Router (传统方式)
核心架构 基于 React Server Components (RSC) 基于客户端组件 (Client Components)
目录结构 app/ pages/
文件约定 page.tsx, layout.tsx, loading.tsx, error.tsx, template.tsx, route.ts _app.tsx, _document.tsx, 文件名即路由 (e.g., about.tsx)
路由定义 文件夹定义路由,page.tsx 作为 UI 文件定义路由 (e.g., pages/about.tsx -> /about)
布局机制 内置且嵌套的 layout.tsx 文件,状态在导航时保留 需要手动创建共享组件或使用 _app.tsx,状态可能丢失
数据获取 在 Server Components 中直接使用 async/awaitfetch getServerSideProps, getStaticProps, getInitialProps
渲染模式 默认是 Server Components (服务端渲染),可选择性使用 "use client" 需明确指定 SSR, SSG, CSR, ISR
API 路由 route.ts 文件 (支持所有 HTTP 方法) pages/api/ 目录下的文件 (一个文件一个端点)
加载状态 内置 loading.tsx,自动应用 React Suspense 需要手动实现加载状态 UI
错误处理 内置 error.tsx,自动应用 React Error Boundary 需要自定义错误边界或使用 _error.tsx
SEO 和元数据 generateMetadata 函数或静态 metadata 对象 使用 <Head> 组件 (next/head)
异步性 组件级别可以是 async 页面级别的数据获取函数是 async

2、详细对比与代码示例

2.1、目录结构和路由定义

Pages Router: 文件即路由。

  • pages/index.tsx -> /
  • pages/about.tsx -> /about
  • pages/blog/[slug].tsx -> /blog/:slug

App Router: 文件夹定义路由,page.tsx 文件用于呈现该路由的 UI。

  • app/page.tsx -> /
  • app/about/page.tsx -> /about
  • app/blog/[slug]/page.tsx -> /blog/:slug

2.2、布局 (Layouts)

Pages Router:

布局通常在 pages/_app.tsx 中实现,或者在每个页面中手动导入一个共享的 Layout 组件。在页面切换时,整个页面会重新渲染,Layout 组件的状态可能会丢失(除非在 _app.tsx 中巧妙处理)。

pages/_app.tsx 示例:

tsx 复制代码
import Layout from '../components/Layout';
import type { AppProps } from 'next/app';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

export default MyApp;

components/Layout.tsx:

tsx 复制代码
import { ReactNode } from 'react';

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <div>
      <nav>
        {/* Navigation bar */}
      </nav>
      <main>{children}</main>
    </div>
  );
}

App Router:

引入了内置的 layout.tsx 文件。布局是分层和嵌套的,当用户在属于同一个布局的路由之间导航时,布局本身不会重新渲染,其状态(如输入框内容、滚动位置)得以保留。

app/layout.tsx (根布局):

tsx 复制代码
import { ReactNode } from 'react';

// 这是必须的根布局,会应用到所有路由
export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <header>My Website Header</header>
        {children}
        <footer>My Website Footer</footer>
      </body>
    </html>
  );
}

app/dashboard/layout.tsx (嵌套布局):

tsx 复制代码
import { ReactNode } from 'react';

export default function DashboardLayout({ children }: { children: ReactNode }) {
  return (
    <section>
      <nav>
        {/* Dashboard specific navigation */}
      </nav>
      {children}
    </section>
  );
}

2.3、数据获取

Pages Router:

pages/posts/[id].tsx 示例 (SSR):

tsx 复制代码
import { GetServerSideProps } from 'next';

type Post = {
  id: string;
  title: string;
  body: string;
};

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { id } = context.params as { id: string };
  const res = await fetch(`https://api.example.com/posts/${id}`);
  const post: Post = await res.json();

  return {
    props: { post },
  };
};

export default function PostPage({ post }: { post: Post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

App Router:

app/posts/[id]/page.tsx 示例:

tsx 复制代码
type Post = {
  id: string;
  title: string;
  body: string;
};

async function getPost(id: string): Promise<Post> {
  const res = await fetch(`https://api.example.com/posts/${id}`, { cache: 'no-store' });
  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }
  return res.json();
}

// 页面组件本身可以是 async 函数
export default async function PostPage({ params }: { params: { id: string } }) {
  const post = await getPost(params.id);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

2.4、服务端组件 vs 客户端组件

app/components/Counter.tsx (客户端组件):

tsx 复制代码
"use client";

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      You clicked {count} times
    </button>
  );
}

app/dashboard/page.tsx (服务端组件,使用客户端组件):

tsx 复制代码
import Counter from '../components/Counter';

// 这是一个服务端组件 (默认)
async function getSomeData(): Promise<{ message: string }> {
  return { message: "Hello from the server!" };
}

export default async function DashboardPage() {
  const data = await getSomeData();

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Server-side data: {data.message}</p>
      <Counter />
    </div>
  );
}

2.5、API 路由 (API Routes)

pages/api/hello.ts:

tsx 复制代码
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    res.status(200).json({ name: 'John Doe' });
  } else {
    res.status(405).end(); // Method Not Allowed
  }
}

app/api/hello/route.ts:

tsx 复制代码
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({ name: 'John Doe' });
}

export async function POST(request: Request) {
  const body = await request.json();
  return NextResponse.json({ message: 'Data received', body });
}

2.6、加载与错误处理

app/dashboard/loading.tsx:

tsx 复制代码
export default function Loading() {
  return <p>Loading dashboard data...</p>;
}

app/dashboard/error.tsx:

tsx 复制代码
"use client";

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>Something went wrong in the dashboard!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

3、选择哪个 Router?

  • 新项目: 强烈推荐使用 App Router。它是 Next.js 的未来,提供了更优的性能(更小的客户端包体积)、更好的开发体验(组件级数据获取、内置布局和加载/错误状态)和更灵活的架构。
  • 现有项目: 如果你的项目已经使用 Pages Router 并且稳定运行,可以不必急于迁移。Next.js 官方支持在同一个项目中同时使用两种 Router,你可以逐步将新的功能或页面用 App Router 来构建。

App Router 的学习曲线相对陡峭一些,因为它引入了 React Server Components 这个新概念。但一旦掌握,它所带来的优势是巨大的。

4、写在最后

如果有遗漏和其他看法欢迎各位大佬补充说明!

好了,今天就分享到这了。Bye~~

相关推荐
老王以为3 分钟前
从源码到架构:React useActionState 深度剖析
前端·javascript·react.js
Ticnix1 小时前
手把手教你在 Next.js 中接入本地大模型,实现 ChatGPT 同款流式对话
前端·next.js
天蓝色的鱼鱼1 小时前
当AI开始替我写代码,我还要纠结选Vue还是React吗?
vue.js·react.js·ai编程
空中海17 小时前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
空中海17 小时前
05 React架构设计、项目实践与专家清单
前端·react.js·前端框架
空中海20 小时前
04 工程化、质量体系与 React 生态
前端·ubuntu·react.js
空中海20 小时前
03 性能、动画与 React Native 新架构
react native·react.js·架构
空中海1 天前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海1 天前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
郑生zs1 天前
Hooks-useEffect
react.js