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 → /addressapp/
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
| 特性 | template | layout |
|---|---|---|
| 路由切换时状态 | 重新创建 | 保持 |
| 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/123app/
orderInfo/
[orderId]/page.tsx → /orderInfo/123tsx
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(捕获后续所有路径片段)路由跳转
Link 组件(推荐)
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 → /productsapp/
(marketing)/
about/page.tsx → /about
contact/page.tsx → /contact
(shop)/
products/page.tsx → /products
Cao Note