Next.js路由基础 #
一、文件系统路由 #
1.1 路由概念 #
Next.js 使用文件系统作为路由基础,app 目录下的文件夹结构直接映射为 URL 路径:
text
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/:slug
└── shop/
└── [category]/
└── [id]/
└── page.tsx → /shop/:category/:id
1.2 页面定义 #
每个路由段需要一个 page.tsx 文件来定义该路由的页面:
tsx
export default function HomePage() {
return <div>首页内容</div>
}
1.3 路由段类型 #
| 类型 | 语法 | 示例 |
|---|---|---|
| 静态路由 | 文件夹名 | /about |
| 动态路由 | [param] |
/blog/[slug] |
| 捕获所有路由 | [...param] |
/docs/[...slug] |
| 可选捕获路由 | [[...param]] |
/docs/[[...slug]] |
| 路由组 | (group) |
/(marketing) |
| 并行路由 | @slot |
/dashboard/@team |
二、Link组件 #
2.1 基本用法 #
tsx
import Link from 'next/link'
export default function Navigation() {
return (
<nav>
<Link href="/">首页</Link>
<Link href="/about">关于</Link>
<Link href="/blog">博客</Link>
</nav>
)
}
2.2 动态链接 #
tsx
import Link from 'next/link'
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>
{post.title}
</Link>
</li>
))}
</ul>
)
}
2.3 Link属性 #
tsx
<Link
href="/dashboard"
className="nav-link"
prefetch={true}
replace={false}
scroll={true}
>
仪表盘
</Link>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| href | string/object | 必填 | 目标路径 |
| replace | boolean | false | 替换历史记录 |
| scroll | boolean | true | 导航时滚动到顶部 |
| prefetch | boolean/null | null | 预加载页面 |
| shallow | boolean | false | 浅层路由 |
2.4 对象形式路径 #
tsx
<Link
href={{
pathname: '/search',
query: { q: 'nextjs', category: 'tech' },
}}
>
搜索 Next.js
</Link>
2.5 带样式的Link #
tsx
import Link from 'next/link'
import { cn } from '@/lib/utils'
interface NavLinkProps {
href: string
children: React.ReactNode
active?: boolean
}
export function NavLink({ href, children, active }: NavLinkProps) {
return (
<Link
href={href}
className={cn(
'px-4 py-2 rounded-md transition-colors',
active
? 'bg-blue-500 text-white'
: 'text-gray-600 hover:bg-gray-100'
)}
>
{children}
</Link>
)
}
三、编程式导航 #
3.1 useRouter Hook #
tsx
'use client'
import { useRouter } from 'next/navigation'
export default function LoginForm() {
const router = useRouter()
const handleLogin = async () => {
const success = await login()
if (success) {
router.push('/dashboard')
}
}
return (
<button onClick={handleLogin}>
登录
</button>
)
}
3.2 useRouter方法 #
tsx
'use client'
import { useRouter } from 'next/navigation'
export default function NavigationButtons() {
const router = useRouter()
return (
<div className="flex gap-2">
<button onClick={() => router.push('/about')}>
前往关于页
</button>
<button onClick={() => router.replace('/login')}>
替换为登录页
</button>
<button onClick={() => router.back()}>
返回上一页
</button>
<button onClick={() => router.forward()}>
前进
</button>
<button onClick={() => router.refresh()}>
刷新当前路由
</button>
</div>
)
}
| 方法 | 说明 |
|---|---|
| push(href) | 导航到新页面 |
| replace(href) | 替换当前页面 |
| back() | 返回上一页 |
| forward() | 前进一页 |
| refresh() | 刷新当前路由 |
3.3 重定向 #
服务端重定向
tsx
import { redirect } from 'next/navigation'
export default async function Page() {
const session = await getSession()
if (!session) {
redirect('/login')
}
return <div>受保护的内容</div>
}
客户端重定向
tsx
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
export default function Page() {
const router = useRouter()
useEffect(() => {
const timer = setTimeout(() => {
router.push('/home')
}, 3000)
return () => clearTimeout(timer)
}, [router])
return <div>3秒后自动跳转...</div>
}
3.4 永久重定向 #
tsx
import { permanentRedirect } from 'next/navigation'
export default function OldPage() {
permanentRedirect('/new-page')
}
四、获取当前路径 #
4.1 服务端获取 #
tsx
import { headers } from 'next/headers'
export default async function Page() {
const headersList = await headers()
const pathname = headersList.get('x-pathname') || ''
return <div>当前路径: {pathname}</div>
}
4.2 客户端获取 #
tsx
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
export default function CurrentPath() {
const pathname = usePathname()
const searchParams = useSearchParams()
const query = searchParams.get('q')
return (
<div>
<p>路径: {pathname}</p>
<p>查询参数: {query}</p>
</div>
)
}
4.3 useSearchParams详解 #
tsx
'use client'
import { useSearchParams } from 'next/navigation'
export default function SearchPage() {
const searchParams = useSearchParams()
const query = searchParams.get('q')
const category = searchParams.get('category')
const page = searchParams.get('page') || '1'
const tags = searchParams.getAll('tag')
return (
<div>
<p>搜索: {query}</p>
<p>分类: {category}</p>
<p>页码: {page}</p>
<p>标签: {tags.join(', ')}</p>
</div>
)
}
五、路由参数 #
5.1 动态路由参数 #
tsx
interface PageProps {
params: Promise<{ slug: string }>
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params
const post = await getPost(slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
5.2 多个动态参数 #
tsx
interface PageProps {
params: Promise<{ category: string; id: string }>
}
export default async function ProductPage({ params }: PageProps) {
const { category, id } = await params
const product = await getProduct(category, id)
return (
<div>
<h1>{product.name}</h1>
<p>分类: {category}</p>
<p>ID: {id}</p>
</div>
)
}
5.3 捕获所有路由 #
tsx
interface PageProps {
params: Promise<{ slug: string[] }>
}
export default async function DocsPage({ params }: PageProps) {
const { slug } = await params
const path = slug.join('/')
return <div>文档路径: {path}</div>
}
URL映射:
text
/docs/getting-started → slug: ['getting-started']
/docs/api/reference → slug: ['api', 'reference']
/docs/guide/basics/intro → slug: ['guide', 'basics', 'intro']
5.4 可选捕获路由 #
tsx
interface PageProps {
params: Promise<{ slug?: string[] }>
}
export default async function OptionalDocs({ params }: PageProps) {
const { slug } = await params
if (!slug) {
return <div>文档首页</div>
}
return <div>文档路径: {slug.join('/')}</div>
}
六、生成静态参数 #
6.1 generateStaticParams #
为动态路由预生成静态页面:
tsx
interface PageProps {
params: Promise<{ slug: string }>
}
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params
const post = await getPost(slug)
return <article>{post.content}</article>
}
6.2 多参数生成 #
tsx
export async function generateStaticParams() {
const products = await getProducts()
return products.map((product) => ({
category: product.category,
id: product.id.toString(),
}))
}
6.3 捕获所有路由生成 #
tsx
export async function generateStaticParams() {
return [
{ slug: ['getting-started'] },
{ slug: ['api', 'reference'] },
{ slug: ['guide', 'basics', 'intro'] },
]
}
七、路由配置 #
7.1 路由段配置 #
tsx
export const dynamic = 'force-dynamic'
export const dynamicParams = true
export const revalidate = 60
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
| 配置 | 值 | 说明 |
|---|---|---|
| dynamic | ‘auto’ | ‘force-dynamic’ | ‘error’ | ‘force-static’ | 动态渲染策略 |
| dynamicParams | boolean | 允许动态参数 |
| revalidate | number | false | 重新验证时间 |
| fetchCache | ‘auto’ | ‘default-cache’ | ‘only-cache’ | … | 获取缓存策略 |
| runtime | ‘nodejs’ | ‘edge’ | 运行时环境 |
| preferredRegion | string | string[] | 首选区域 |
7.2 实例配置 #
tsx
export const dynamic = 'force-static'
export const revalidate = 3600
export default async function Page() {
const data = await getData()
return <div>{data}</div>
}
八、路由事件 #
8.1 路由变化监听 #
tsx
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
export function RouteChangeLogger() {
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
console.log('路由变化:', pathname)
console.log('查询参数:', searchParams.toString())
}, [pathname, searchParams])
return null
}
8.2 页面访问统计 #
tsx
'use client'
import { usePathname } from 'next/navigation'
import { useEffect } from 'react'
export function Analytics() {
const pathname = usePathname()
useEffect(() => {
if (typeof window !== 'undefined') {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: pathname,
})
}
}, [pathname])
return null
}
九、最佳实践 #
9.1 导航组件封装 #
tsx
import Link from 'next/link'
import { usePathname } from 'next/navigation'
const navItems = [
{ href: '/', label: '首页' },
{ href: '/about', label: '关于' },
{ href: '/blog', label: '博客' },
{ href: '/contact', label: '联系' },
]
export function Navigation() {
const pathname = usePathname()
return (
<nav className="flex gap-4">
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className={pathname === item.href ? 'active' : ''}
>
{item.label}
</Link>
))}
</nav>
)
}
9.2 面包屑导航 #
tsx
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export function Breadcrumb() {
const pathname = usePathname()
const segments = pathname.split('/').filter(Boolean)
return (
<nav className="flex items-center gap-2 text-sm">
<Link href="/">首页</Link>
{segments.map((segment, index) => {
const href = `/${segments.slice(0, index + 1).join('/')}`
const isLast = index === segments.length - 1
return (
<span key={href} className="flex items-center gap-2">
<span>/</span>
{isLast ? (
<span className="text-gray-500">{segment}</span>
) : (
<Link href={href}>{segment}</Link>
)}
</span>
)
})}
</nav>
)
}
十、总结 #
路由基础要点:
| 要点 | 说明 |
|---|---|
| 文件系统路由 | 目录结构映射URL |
| Link组件 | 声明式导航 |
| useRouter | 编程式导航 |
| 动态路由 | [param]语法 |
| 路由参数 | params获取 |
| 路由配置 | 段配置选项 |
下一步,让我们深入学习动态路由!
最后更新:2026-03-28