Skip to content
第 158 / 250 章前端⏱ 10 分钟阅读

第 158 章:Next.js App Router 深入

学习目标

  • 深入理解 App Router 约定
  • 掌握 layout / loading / error 特殊文件
  • 学会路由组 (group)
  • 理解平行路由与拦截路由

一、App Router 目录约定

二、特殊文件

文件作用
layout.tsx共享布局(嵌套)
page.tsx路由 UI
loading.tsx加载 UI(基于 Suspense)
error.tsx错误边界
not-found.tsx404 UI
template.tsx每次导航都重新挂载
default.tsx平行路由的 fallback

三、layout 嵌套

tsx
// app/layout.tsx(根布局,必须包含 html/body)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx(只包裹 dashboard)
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  );
}

四、loading 加载状态

tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return <div className="p-8">加载中...</div>;
}

// 自动用 React Suspense 包裹 page.tsx

五、error 错误边界

tsx
'use client';   // 错误边界必须是 Client Component

// app/dashboard/error.tsx
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>出错了</h2>
      <p>{error.message}</p>
      <button onClick={reset}>重试</button>
    </div>
  );
}

六、not-found 404

tsx
// app/not-found.tsx
import Link from 'next/link';

export default function NotFound() {
  return (
    <div className="p-8 text-center">
      <h2>404 - 页面未找到</h2>
      <Link href="/">回首页</Link>
    </div>
  );
}

七、路由组 (group)

(folder) 命名的目录不影响 URL,只用于组织代码和共享布局。

app/
├── (marketing)/
│   ├── layout.tsx       # 营销页布局
│   ├── about/
│   │   └── page.tsx     # /about
│   └── pricing/
│       └── page.tsx     # /pricing
├── (app)/
│   ├── layout.tsx       # 应用页布局
│   ├── dashboard/
│   │   └── page.tsx     # /dashboard
│   └── settings/
│       └── page.tsx     # /settings

八、动态路由

8.1 单段动态

app/blog/[slug]/page.tsx     → /blog/hello-world
tsx
// app/blog/[slug]/page.tsx
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  return <h1>文章:{slug}</h1>;
}

8.2 多段动态

app/shop/[category]/[id]/page.tsx  → /shop/phone/123
tsx
export default async function Product({ params }: { params: Promise<{ category: string; id: string }> }) {
  const { category, id } = await params;
  return <h1>{category} - {id}</h1>;
}

8.3 Catch-all

app/docs/[...slug]/page.tsx   → /docs/a/b/c/d
tsx
export default async function Doc({ params }: { params: Promise<{ slug: string[] }> }) {
  const { slug } = await params;
  return <pre>{slug.join('/')}</pre>;
}
tsx
import Link from 'next/link';

// 基础
<Link href="/about">关于</Link>

// 动态
<Link href={`/blog/${post.slug}`}>{post.title}</Link>

// prefetch(默认自动)
<Link href="/dashboard" prefetch={false}>仪表盘</Link>

// 替换
<Link href="/login" replace>登录</Link>

useRouter

tsx
'use client';
import { useRouter } from 'next/navigation';

function Component() {
  const router = useRouter();
  return <button onClick={() => router.push('/about')}>跳转</button>;
}

十、usePathname 与 useSearchParams

tsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';

function NavItem() {
  const pathname = usePathname();
  const params = useSearchParams();
  const isActive = pathname === '/dashboard';
  const filter = params.get('filter');
  return <div>{pathname} - {filter}</div>;
}

十一、redirect 与 notFound

tsx
import { redirect, notFound } from 'next/navigation';

// Server Component
async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await fetchPost(id);
  if (!post) notFound();                  // 渲染 not-found.tsx
  if (!user) redirect('/login');          // 重定向
  return <div>{post.title}</div>;
}

十二、本章小结

概念关键
特殊文件layout / page / loading / error
路由组(group) 不影响 URL
动态路由[slug] [...slug]
导航Link useRouter
重定向redirect notFound

动手练习

  1. 实现一个 (marketing)(app) 路由组,不同布局
  2. /dashboard 加 loading.tsx 和 error.tsx
  3. 实现 /blog/[slug] 动态路由
  4. 实现 404 页面

推荐阅读


下一章:第 159 章:路由与布局

本站基于 VitePress 构建 · 由 Codebook 团队维护