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自定义Hooks设计指南:从封装到复用
前端·javascript·react.js
小菜全11 小时前
《React vs Vue:选择适合你的前端框架》
vue.js·react.js·前端框架
duansamve16 小时前
React 18项目中使用环境变量(适用于不同环境下的配置常量)
react.js
小刘鸭地下城19 小时前
告别服务端渲染卡顿!useSyncExternalStore 优化水合作用全解析
react.js
Winson℡20 小时前
如何理解 React Native 中的 useEffect
javascript·react native·react.js
乖女子@@@1 天前
React-props的children属性
前端·javascript·react.js
八月十八1 天前
React常用Hooks及使用示例大全
前端·javascript·react.js
wayne2141 天前
React Native启动性能优化实战:Hermes + RAM Bundles + 懒加载
react native·react.js·性能优化
殇蓝1 天前
react-lottie动画组件封装
前端·react.js·前端框架