外观
链接和导航
链接和导航
在Next.js中,有四种方式可以在路由之间进行导航:
- 使用
<Link>
组件 - 使用
useRouter
钩子(客户端组件) - 使用
redirect
函数(服务器组件) - 使用原生History API
本页将介绍如何使用这些选项,并深入探讨导航的工作原理。
<Link>
组件
<Link>
是一个内置组件,它扩展了HTML的<a>
标签,提供了路由之间的预取和客户端导航功能。它是在Next.js中在路由之间导航的主要和推荐方式。
您可以通过从next/link
导入并将href
属性传递给组件来使用它:
app/page.tsx
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
app/page.tsx
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
您可以向<Link>
传递其他可选属性。查看API参考了解更多信息。
示例
链接到动态段
当链接到动态段时,您可以使用模板字面量和插值来生成链接列表。例如,要生成博客文章列表:
app/blog/PostList.js
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>
)
}
检查活动链接
您可以使用usePathname()
来确定链接是否处于活动状态。例如,要为活动链接添加一个类,您可以检查当前的pathname
是否与链接的href
匹配:
@/app/ui/nav-links.tsx
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}
滚动到一个id
Next.js应用路由器的默认行为是滚动到新路由的顶部,或在后退和前进导航时保持滚动位置。
如果您想在导航时滚动到特定的id
,可以在URL后面附加一个#
哈希链接,或者直接将哈希链接传递给href
属性。这是可能的,因为<Link>
渲染为一个<a>
元素。
<Link href="/dashboard#settings">Settings</Link>
// Output
<a href="/dashboard#settings">Settings</a>
提示
如果导航时页面不在视口中,Next.js将滚动到该页面。
禁用滚动恢复
Next.js应用路由器的默认行为是滚动到新路由的顶部,或在后退和前进导航时保持滚动位置。 如果您想禁用这个行为,可以向<Link>
组件传递scroll={false}
,或者在router.push()
或router.replace()
中传递scroll: false
。
// next/link
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
useRouter()
钩子
useRouter
钩子允许您以编程方式从客户端组件改变路由。
app/page.js
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
有关useRouter
方法的完整列表,请参阅API参考。
提示
除非您有使用useRouter
的特定需求,否则请使用<Link>
组件在路由之间导航。
redirect
函数
对于服务器组件,请使用redirect
函数。
app/team/[id]/page.tsx
import { redirect } from 'next/navigation'
async function fetchTeam(id: string) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}
export default async function Profile({ params }: { params: { id: string } }) {
const team = await fetchTeam(params.id)
if (!team) {
redirect('/login')
}
// ...
}
app/team/[id]/page.js
import { redirect } from 'next/navigation'
async function fetchTeam(id) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}
export default async function Profile({ params }) {
const team = await fetchTeam(params.id)
if (!team) {
redirect('/login')
}
// ...
}
提示
redirect
默认返回307(临时重定向)状态码。在服务器操作中使用时,它返回303(查看其他),通常用于在POST请求后重定向到成功页面。redirect
内部抛出一个错误,因此应该在try/catch
块之外调用。redirect
可以在客户端组件的渲染过程中调用,但不能在事件处理程序中调用。您可以使用useRouter
钩子代替。redirect
也接受绝对URL,可以用于重定向到外部链接。- 如果您想在渲染过程之前重定向,请使用
next.config.js
或中间件。
有关更多信息,请参阅redirect
API参考。
使用原生History API
Next.js允许您使用原生的window.history.pushState
和window.history.replaceState
方法来更新浏览器的历史堆栈,而无需重新加载页面。
pushState
和replaceState
调用集成到Next.js路由器中,允许您与usePathname
和useSearchParams
同步。
window.history.pushState
使用它可以向浏览器的历史堆栈添加新条目。用户可以导航回到前一个状态。例如,对产品列表进行排序:
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder) {
const params = new URLSearchParams(searchParams.toString())
params.set('sort', sortOrder)
window.history.pushState(null, '', `?${params.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
window.history.replaceState
使用它可以替换浏览器历史堆栈上的当前条目。用户无法导航回到前一个状态。例如,切换应用程序的语言环境:
'use client'
import { usePathname } from 'next/navigation'
export function LocaleSwitcher() {
const pathname = usePathname()
function switchLocale(locale) {
// e.g. '/en/about' or '/fr/contact'
const newPath = `/${locale}${pathname}`
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}
路由和导航的工作原理
应用路由器使用混合方法进行路由和导航。在服务器上,您的应用程序代码按路由段自动进行代码分割。在客户端,Next.js预取并缓存路由段。这意味着,当用户导航到新路由时,浏览器不会重新加载页面,只有变化的路由段会重新渲染 - 改善了导航体验和性能。
1. 代码分割
代码分割允许您将应用程序代码分割成更小的包,以便浏览器下载和执行。这减少了每个请求的数据传输量和执行时间,从而提高了性能。
服务器组件允许您的应用程序代码按路由段自动进行代码分割。这意味着在导航时只加载当前路由所需的代码。
2. 预取
预取是一种在用户访问路由之前在后台预加载该路由的方法。
在Next.js中,有两种预取路由的方式:
<Link>
组件: 路由在进入用户视口时自动预取。预取发生在页面首次加载时或通过滚动进入视图时。router.prefetch()
:useRouter
钩子可用于以编程方式预取路由。
<Link>
的默认预取行为(即当prefetch
属性未指定或设置为null
时)根据您使用loading.js
的情况而有所不同。只有共享布局,直到渲染的"树"中第一个loading.js
文件,才会被预取和缓存30s
。这减少了获取整个动态路由的成本,并意味着您可以显示即时加载状态,为用户提供更好的视觉反馈。
您可以通过将prefetch
属性设置为false
来禁用预取。或者,您可以通过将prefetch
属性设置为true
来预取加载边界之外的完整页面数据。
有关更多信息,请参阅<Link>
API参考。
提示
- 预取在开发环境中不启用,只在生产环境中启用。
3. 缓存
Next.js拥有一个称为路由缓存的内存客户端缓存。当用户在应用中导航时,预取的路由段和已访问路由的React服务器组件有效负载会被存储在缓存中。
这意味着在导航时,缓存会被尽可能地重用,而不是向服务器发起新的请求 - 通过减少请求数量和传输的数据量来提高性能。
了解更多关于路由缓存的工作原理及其配置方法。
4. 部分渲染
部分渲染意味着在导航时,只有发生变化的路由段会在客户端重新渲染,而共享的段会被保留。
例如,当在两个兄弟路由/dashboard/settings
和/dashboard/analytics
之间导航时,settings
和analytics
页面将被渲染,而共享的dashboard
布局将被保留。
如果没有部分渲染,每次导航都会导致整个页面在客户端重新渲染。仅渲染发生变化的段可以减少传输的数据量和执行时间,从而提高性能。
5. 软导航
浏览器在页面之间导航时会执行"硬导航"。Next.js的App Router启用了页面之间的"软导航",确保只有发生变化的路由段被重新渲染(部分渲染)。这使得客户端React状态在导航过程中得以保留。
6. 前进后退导航
默认情况下,Next.js会在前进和后退导航时维持滚动位置,并重用路由缓存中的路由段。
7. pages/
和app/
之间的路由
当从pages/
逐步迁移到app/
时,Next.js路由器将自动处理两者之间的硬导航。为了检测从pages/
到app/
的转换,有一个客户端路由器过滤器,它利用app路由的概率检查,这可能偶尔会导致误报。默认情况下,这种情况应该非常罕见,因为我们将误报概率配置为0.01%。可以通过next.config.js
中的experimental.clientRouterFilterAllowedRate
选项自定义这个概率。需要注意的是,降低误报率将增加客户端包中生成的过滤器的大小。
或者,如果你更倾向于完全禁用此处理并手动管理pages/
和app/
之间的路由,可以在next.config.js
中将experimental.clientRouterFilter
设置为false。当禁用此功能时,pages中与app路由重叠的任何动态路由默认将无法正确导航。