Skip to content

NextJs 路由 ​

Next.js 拥有两套路由系统(两者兼容):

  • Pages Router — 在 pages 目录下创建文件即对应路由
  • App Router — Next.js 13.4+ 默认路由模式,新增 layout、loading、error 等约定文件

页面路由(App Router) ​

app/
  page.tsx          →  /
  about/page.tsx    →  /about
  address/page.tsx  →  /address
app/
  page.tsx          →  /
  about/page.tsx    →  /about
  address/page.tsx  →  /address

页面文件约定使用 page.tsx:

tsx
export default function Home() {
  return <h1>首页</h1>
}
export default function Home() {
  return <h1>首页</h1>
}

布局 layout ​

布局(layout)包裹子页面,切换路由时保持状态,支持嵌套:

tsx
// app/layout.tsx(根布局,必须包含 html 和 body)
export const metadata = { title: 'My App' }

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
// app/layout.tsx(根布局,必须包含 html 和 body)
export const metadata = { title: 'My App' }

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
tsx
// app/address/layout.tsx(嵌套布局)
export default function AddressLayout({ children }) {
  return <div className="address-layout">{children}</div>
}
// app/address/layout.tsx(嵌套布局)
export default function AddressLayout({ children }) {
  return <div className="address-layout">{children}</div>
}

模板 template ​

模板与 layout 用法相同,但切换路由时重新渲染:

tsx
'use client'
import { useState } from 'react'

export default function AddressTemplate({ children }) {
  const [count, setCount] = useState(0)
  return (
    <>
      <h1>Template 点击次数: {count}</h1>
      <button onClick={() => setCount(count + 1)}>点击</button>
      {children}
    </>
  )
}
'use client'
import { useState } from 'react'

export default function AddressTemplate({ children }) {
  const [count, setCount] = useState(0)
  return (
    <>
      <h1>Template 点击次数: {count}</h1>
      <button onClick={() => setCount(count + 1)}>点击</button>
      {children}
    </>
  )
}

template vs layout ​

特性templatelayout
路由切换时状态重新创建保持
Suspense fallback每次切换都会触发只会触发一次
使用场景需要每次重新初始化的内容导航栏、侧边栏等持续存在的 UI

loading 与错误处理 ​

loading.tsx(加载状态) ​

基于 Suspense 实现,页面加载时显示:

tsx
export default function Loading() {
  return <h1>loading....</h1>
}
export default function Loading() {
  return <h1>loading....</h1>
}

error.tsx(错误边界) ​

tsx
'use client'

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>出错了</h2>
      <button onClick={reset}>重试</button>
    </div>
  )
}
'use client'

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>出错了</h2>
      <button onClick={reset}>重试</button>
    </div>
  )
}

not-found.tsx(404 页面) ​

tsx
export default function NotFound() {
  return <h1>404 - 页面未找到</h1>
}
export default function NotFound() {
  return <h1>404 - 页面未找到</h1>
}

动态路由 ​

使用方括号 [] 表示动态参数:

app/
  orderInfo/
    [orderId]/page.tsx  →  /orderInfo/123
app/
  orderInfo/
    [orderId]/page.tsx  →  /orderInfo/123
tsx
export default function OrderDetail({ params }) {
  return <h1>订单详情: {params.orderId}</h1>
}
export default function OrderDetail({ params }) {
  return <h1>订单详情: {params.orderId}</h1>
}

捕获所有路由 ​

diff
- [orderId]     → 匹配 /orderInfo/123
+ [[...orderId]] → 匹配 /orderInfo/123/shop(捕获后续所有路径片段)
- [orderId]     → 匹配 /orderInfo/123
+ [[...orderId]] → 匹配 /orderInfo/123/shop(捕获后续所有路径片段)

路由跳转 ​

tsx
import Link from 'next/link'

<Link href="/about">关于</Link>
<Link href="/parent/sonA" scroll={false}>不滚动到顶部</Link>
import Link from 'next/link'

<Link href="/about">关于</Link>
<Link href="/parent/sonA" scroll={false}>不滚动到顶部</Link>

useRouter ​

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

function Page() {
  const router = useRouter()
  return <button onClick={() => router.push('/about')}>跳转</button>
}
'use client'
import { useRouter } from 'next/navigation'

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

redirect(服务端) ​

ts
import { redirect } from 'next/navigation'

async function login() {
  const res = await login()
  if (!res.ok) redirect('/error')
}
import { redirect } from 'next/navigation'

async function login() {
  const res = await login()
  if (!res.ok) redirect('/error')
}

路由组 ​

使用 (groupName) 创建路由组,不影响 URL 路径:

app/
  (marketing)/
    about/page.tsx     →  /about
    contact/page.tsx   →  /contact
  (shop)/
    products/page.tsx  →  /products
app/
  (marketing)/
    about/page.tsx     →  /about
    contact/page.tsx   →  /contact
  (shop)/
    products/page.tsx  →  /products