App Router 是什么?(Next.js 13+ 路由系统指南)

December 28, 2025
App Router 是 Next.js 13+ 的新路由与渲染架构,基于 app 目录、嵌套布局与 React Server Components,支持 Streaming、并行路由与更清晰的数据获取模式。
Next.js Core

什么是 App Router?

App Router 是 Next.js 13+ 引入的新路由系统与应用架构,使用 app/ 目录进行约定式路由(文件即路由),并以 React Server Components(RSC) 为默认能力,重新组织了布局(Layout)、页面(Page)、加载态(Loading)、错误处理(Error)与数据获取方式。

如果你把 Pages Router 看作“以页面为中心(pages/)”,那么 App Router 更像“以路由段(route segment)与布局树为中心(app/)”:每个路由段都可以拥有自己的布局、加载与错误边界,最终拼装成完整的 UI 树。

为什么重要?

  1. 更强的布局与导航体验:支持嵌套布局、持久化布局与局部刷新,减少重复渲染与重复请求。
  2. 默认 Server-first(更省 JS):页面默认是 Server Component,可把更多渲染与数据读取放到服务端,减少客户端 bundle。
  3. 更好的加载与错误边界:每个路由段可独立定义 loading.tsx / error.tsx,提升用户体验与可维护性。
  4. 更自然的全栈能力:Route Handlers(app/api/**/route.ts)、Server Actions 让“同仓全栈”更顺滑。
  5. 为未来特性铺路:并行路由、拦截路由、Streaming/Suspense 等能力,在 App Router 下更完整。

核心概念/工作原理

路由段与约定文件

在 App Router 中,一个 URL 通常由 app/ 下的目录结构决定:

  • page.tsx:该段的页面入口
  • layout.tsx:该段与其子段共享的布局(可嵌套)
  • loading.tsx:该段的加载 UI(基于 Suspense)
  • error.tsx:该段的错误边界(必须是 Client Component)
  • not-found.tsx:该段的 404 页面

一个最小示例:

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>{children}</body>
    </html>
  );
}
// app/page.tsx
export default function HomePage() {
  return <main>首页</main>;
}

Server Components vs Client Components

  • 默认是 Server Component:不写 "use client" 的组件会在服务端渲染,可直接访问服务器资源(数据库、私有 API、环境变量等)。
  • 需要交互才用 Client Component:例如按钮点击、表单即时校验、使用 useState/useEffect 等。
// app/dashboard/page.tsx (Server Component,默认)
import { getServerSession } from "@/lib/auth"; // 示例:服务端读取会话

export default async function DashboardPage() {
  const session = await getServerSession();
  return <div>欢迎你,{session.user.name}</div>;
}
// app/dashboard/ClientButton.tsx (Client Component)
"use client";

import { useState } from "react";

export function ClientButton() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>点击:{count}</button>;
}

嵌套布局与共享 UI

App Router 的布局是“树状组合”的。你可以为某一段提供独立布局,同时继承上层布局:

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="dashboard-shell">
      <aside>侧边栏</aside>
      <section>{children}</section>
    </div>
  );
}

数据获取与缓存(直觉版)

在 App Router 中,常见推荐是:

  • 服务端组件直接获取数据(更简单)
  • 对外部 fetch 默认做缓存与去重(可按需设置 cache / revalidate
  • 需要“提交后更新 UI”,常用 Server Actions 或配合 revalidatePath/revalidateTag

(更深入可看 nextjs-server-actionsnextjs-server-components 相关词条。)

实际应用示例

场景 1:构建一个带加载态与错误边界的详情页(动态路由)

// app/posts/[slug]/page.tsx
import { notFound } from "next/navigation";

async function getPost(slug: string) {
  // 示例:从你的 API/数据库读取
  const res = await fetch(`https://example.com/api/posts/${slug}`, { cache: "no-store" });
  if (res.status === 404) return null;
  if (!res.ok) throw new Error("加载失败");
  return res.json();
}

export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await getPost(slug);

  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}
// app/posts/[slug]/loading.tsx
export default function Loading() {
  return <div>正在加载文章...</div>;
}
// app/posts/[slug]/error.tsx
"use client";

export default function Error({ reset }: { reset: () => void }) {
  return (
    <div>
      <p>加载文章时出错。</p>
      <button onClick={() => reset()}>重试</button>
    </div>
  );
}

解释page.tsx 在服务端执行并获取数据;当请求未完成时展示 loading.tsx;抛错会被 error.tsx 捕获;找不到资源用 notFound() 进入该段的 404 逻辑。

场景 2:用 Layout 让“账户区”拥有统一外壳与权限校验

// app/account/layout.tsx
import { redirect } from "next/navigation";
import { getServerSession } from "@/lib/auth"; // 示例

export default async function AccountLayout({ children }: { children: React.ReactNode }) {
  const session = await getServerSession();
  if (!session) redirect("/login");

  return (
    <div className="account">
      <header>账户中心</header>
      <main>{children}</main>
    </div>
  );
}

解释:把“鉴权 + 统一外壳”放到 layout.tsx,可以保证 app/account/** 下的页面都自动受保护,同时减少每个页面重复写权限判断。

最佳实践

💡 建议 1:默认使用 Server Component,只有在需要交互时才下沉到 Client。 这能显著减少客户端 JS,并让数据访问更直观。

  1. 把共享 UI 放在布局层:导航、侧边栏、权限校验、全局提供器等优先写在 layout.tsx
  2. 为关键路由段补齐边界文件:给核心页面加 loading.tsx / error.tsx / not-found.tsx,用户体验会提升一个量级。
  3. 动态路由与数据获取要“可预期”:明确哪些页面需要实时数据(no-store),哪些可以增量更新(revalidate)。
  4. 让路由结构表达业务:例如 app/(marketing)app/(app) 这类 route group(括号目录)可以让 URL 不变但结构更清晰。

⚠️ 常见坑:在 Server Component 里直接使用浏览器 API 或 React hooks(useState/useEffect)。 需要交互的组件请加 "use client" 并隔离边界。

常见问题

App Router 与 Pages Router 必须二选一吗?

答案:可以共存。你可以逐步迁移,把新功能放到 app/,旧页面继续留在 pages/,直到迁移完成。

为什么我的 error.tsx 必须是 Client Component?

答案:因为错误边界依赖客户端错误恢复机制(例如 reset()),Next.js 要求 error.tsx"use client"

layout.tsx 会在每次导航都重新渲染吗?

答案:通常不会。布局可以持久化(尤其是同一布局树下的导航),这是 App Router 提升体验与性能的重要原因之一。

什么时候需要 Route Handlers(app/api/**/route.ts)?

答案:当你需要提供 HTTP API(给第三方回调、Webhook、移动端或外部系统调用)时,Route Handlers 很合适;仅用于表单提交时也可以考虑 Server Actions。

与其他概念的对比

特性App RouterPages Router
路由目录app/pages/
默认渲染模式Server Components 优先客户端/服务端混合(以页面为中心)
布局能力嵌套布局、持久化更强需要手动封装与组合
Loading/Error 边界路由段级别原生支持需要自行实现
新特性承载并行/拦截路由、Server Actions 等支持有限或需要额外方案

进一步学习

相关术语

  • nextjs-server-components
  • nextjs-server-actions
  • nextjs-middleware
  • nextjs-dynamic-routes
  • nextjs-parallel-routes
  • nextjs-metadata-api