Next.js 中的 pathname 与 path:开发者必知

Wayne
作者 Wayne ·

引言

刚开始为一个电商客户做 Next.js 项目时,我总是搞不清楚“pathname”和“path”的区别。看起来可以互换的两个词,其实代表着完全不同的概念,并且会显著影响路由行为。为了解决动态商品页参数总是取不到的 bug,我花了好几个小时调试,最后才发现自己在代码里一直误用了这两个概念。

本文基于我在生产项目中的经验,清晰解释 Next.js 里的 pathname 与 path 分别是什么、各自适用的真实场景、常见误区(包括我自己踩过的坑),以及一系列实用示例。无论你在做简单博客还是复杂应用,搞清这两个概念都能帮你少踩无数坑,同时优化项目的性能与 SEO。

Next.js Pathname 与 Path

Next.js 13+(App Router)的重要变更

在深入讨论 pathname 与 path 之前,必须先强调一个变化:自 Next.js 13 引入 App Router 起,路由系统发生了根本性的改变。使用新版(13、14、15)的同学需要注意以下区别:

在 Pages Router(传统方案)中:

  • 使用 import { useRouter } from 'next/router'
  • 可以读取 router.pathname(路由模板)与 router.asPath(实际 URL 路径)

在 App Router(新方案)中:

  • 使用 import { usePathname } from 'next/navigation'
  • asPath 被移除,因为“新路由里移除了 as 的概念”

在 App Router 里,usePathname 必须在客户端组件中使用,因此需要添加 'use client' 指令:

'use client'
import { usePathname } from 'next/navigation'

export default function Navigation() {
  const pathname = usePathname()
  return <p>Current path: {pathname}</p>
}

需要特别注意的是,与 Pages Router 不同,App Router 中的 usePathname 返回的是“实际路径”(如:/farm/66016e78bc87d1994aebe41b),而不是“路由模板”(如:/farm/[farm_id])。这会影响依赖“路由模板判断”的代码逻辑。

如果你想完整复刻旧的 asPath 功能,还需要配合 useSearchParams 使用,因为 asPath 包含查询参数。但需要注意,这依然不包含 URL 的 hash 片段。

本文会分别从 Pages Router 与 App Router 的角度阐述这些概念,帮助你理解差异并相应调整代码。

什么是 pathname?实战视角

用最简单的话说:在 Next.js 中,pathname 指的是 URL 的“路径部分”,不包含任何查询参数或 hash 片段。它基本等于域名后、问号或井号前的那一段。

例如 URL https://mystore.com/products/shirts?color=blue#reviews 中,pathname 就是 /products/shirts。Next.js 正是用这段干净、有结构的路径来匹配路由。

我第一次真正意识到 pathname 重要性,是在做多分类商品目录时。下面是我在组件里访问 pathname 的常见方式:

import { useRouter } from 'next/router'

function ProductPage() {
  const router = useRouter()
  const currentPathname = router.pathname
  
  console.log(currentPathname) // 输出:/products/[category]/[id]
  
  // 组件的其他逻辑...
}

注意,pathname 返回的是带占位符的“路由模式”([category][id]),不是实际值。这是最常见的误区之一——刚学 Next.js 时它让我困扰了好几天。

我曾犯过的错误是直接从 pathname 里取商品 ID——这是行不通的,因为 pathname 给的是模板,不是实际值。应该用 router.query 访问动态参数:

// 错误做法(我以前用过)
const productId = router.pathname.split('/').pop() // 只能得到 "[id]",拿不到真实 ID!

// 正确做法
const productId = router.query.id // 从 URL 中拿到真实商品 ID

当你要做面包屑、激活态导航高亮,或埋点分析等“需要理解当前路由结构”的功能时,这个区别尤其重要。

“path”的多重含义

与定义明确的 pathname 不同,Next.js 中的“path”会因上下文而异,这正是易混淆的根源。

在我的实践里,path 常见于以下几类:

  1. 文件系统路径:项目目录里文件的物理位置。
  2. 完整 URL 路径:包含查询参数与 hash 片段的完整路径。
  3. 路由器路径:进行编程式导航时使用的路径。

我在为某 SaaS 做文档站时常把这些上下文混在一起。比如用 Link 组件时,我错误地写过:

// 错误写法
<Link href={router.pathname + '?section=advanced'}>
  Advanced Options
</Link>

这当然不如预期,因为 pathname 里包含的是像 [id] 这样的模板占位符,而非真实值。正确写法应当是:

// 正确写法
<Link href={{
  pathname: router.pathname,
  query: { ...router.query, section: 'advanced' }
}}>
  Advanced Options
</Link>

把 pathname 与 path 的视觉区别讲清楚很重要。我常用下面这个例子给团队解释:

URL:https://example.com/blog/posts/123?author=john#comments

  • pathname:/blog/posts/123(只有结构化路径部分)
  • path(完整 URL 路径):/blog/posts/123?author=john#comments(域名后的一切都算)

App Router vs Pages Router:路由系统的演进

随着 Next.js 的演进,理解路由系统的变化很重要。在 Pages Router 里,我们常用 router.pathnamerouter.asPath 的区别来覆盖不同场景;但在 App Router 中,这种区分不复存在。

在 App Router 中,可以用以下替代方案:

  1. 获取当前路径(替代 asPath):
'use client'
import { usePathname, useSearchParams } from 'next/navigation'

function MyComponent() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
  
  // 构造完整路径(类似旧的 asPath)
  const fullPath = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : '')
  
  return <div>Current path: {fullPath}</div>
}
  1. 动态路由参数:在 App Router 中,你无法再通过 pathname 判断“是否为模板”。需要在服务端组件使用约定的模式,或在客户端组件使用 useParams 钩子。

这些变化体现了 Next.js 朝着更干净、职责更清晰的路由系统演进,不过从旧系统迁移过来的同学可能需要一段适应期。

实战案例:电商站点的路由设计

Let me share how these concepts came into play in a real project. When building an e-commerce site with multiple vendors, we needed to structure our URLs for optimal SEO while maintaining clean component architecture.

For product pages, we used a structure like /shop/[vendorSlug]/[productId]. Here's how I properly implemented pathname handling:

function ProductPage() {
  const router = useRouter()
  const { vendorSlug, productId } = router.query
  
  // 埋点时,既记录模板也记录真实路径
  useEffect(() => {
    trackPageView({
      pageTemplate: router.pathname, // → "/shop/[vendorSlug]/[productId]"
      actualPage: router.asPath, // → 浏览器地址栏中的真实路径
      productId
    })
  }, [router.pathname, router.asPath, productId])
  
  // 构造 API 路径时不要用 pathname
  const apiPath = `/api/products/${productId}`
  
  // 组件的其他逻辑...
}

我们遇到过一个 API 调用相关的隐藏 bug:有位同学尝试基于“当前 pathname”去拼相对 API 路径:

// 错误示例
const apiPath = `${router.pathname.replace('[productId]', productId)}/api/details`

这样会得到类似 /shop/vendor-name/123/api/details 的路径,而不是正确的 /api/products/123/details。我强调过很多次:pathname 是“路由结构”,不是用来“拼新路径”的素材。

性能优化小技巧

理解 pathname 与 path 的差异,也帮我们在多个 Next.js 项目里拿到了实打实的性能收益。

其中一个优化是:预获取(prefetch)相关商品页。因为 pathname 给的是“路由模板”,我们就能方便地按相同模式预取:

function ProductThumbnail({ product, relatedProducts }) {
  const router = useRouter()
  
  useEffect(() => {
    // 按相同模板预取相关商品
    relatedProducts.forEach(related => {
      router.prefetch({
        pathname: router.pathname, // 复用当前路由模式
        query: { 
          vendorSlug: related.vendorSlug,
          productId: related.id
        }
      })
    })
  }, [relatedProducts, router])
  
  // 组件 JSX
}

得益于对路由模式匹配的正确使用,这个优化把页面切换耗时降低了约 47%。

常见新手误区

在带新人时,我反复看到与 pathname/path 相关的这些问题:

  1. 混淆 router.pathnamerouter.asPath

    // 错误:得到的是带 [占位符] 的模板
    const currentUrl = router.pathname
    
    // 正确:得到的是包含真实值的实际路径
    const currentUrl = router.asPath
    
  2. 在组件里“硬编码” pathname

    // 不灵活的写法
    <Link href="/products/[id]">Product</Link>
    
    // 更好的写法
    <Link href={{
      pathname: '/products/[id]',
      query: { id: productId }
    }}>Product</Link>
    

我给团队整理过一个快速自检清单:

  • 如果你在 URL 里看到方括号 [ ],说明用到了 pathname,但你其实需要 asPath
  • 如果动态参数取不到,先检查是否使用了 router.query
  • 如果链接跳转后丢了查询参数,可能是你没有正确合并 router.query

结论:如何做正确选择

结合多个项目与经验教训,我的建议是:

  • 需要“路由模板/模式”时用 router.pathname(如埋点、路由匹配、理解页面结构)
  • 需要“浏览器中真实展示的路径”时用 router.asPath
  • 使用 router.query 访问动态路由参数与查询字符串
  • 拼装新路径时,要明确你是基于“模板”还是“真实值”来构建

常见问题(FAQ):Next.js 的 pathname vs path

router.pathnamerouter.asPath 的本质区别是什么?

router.pathname 返回带占位符的路由模板(如 /products/[id]);router.asPath 返回浏览器地址栏中实际展示的路径,包含真实值(如 /products/123?color=blue)。需要“路由模式”用 pathname,需要“包含查询参数的精确当前路径”用 asPath

router.pathname 会包含查询参数吗?

不会。router.pathname 不包含查询参数或 hash 片段,只包含带占位符的基础路径结构。比如 URL 是 /products/shirts?size=xl#detailsrouter.pathname 只会是 /products/shirts。如需获取查询参数,请使用 router.query

pathname 会如何影响 Next.js 应用的 SEO?

pathname 直接决定你的 URL 结构,而 URL 结构对 SEO 至关重要。结构清晰、保持一致的 pathname 有助于搜索引擎理解站点层级与内容关系。建议在路径段里使用描述性、包含关键词的语义化片段(例如用 /blog/javascript/next-js-routing 代替 /blog/post/123),提升可发现性与搜索相关性。