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>
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