在 Next.js 中,有四種方式可以在路由之間導(dǎo)航:
<Link>
組件useRouter
鉤子(客戶端組件)redirect
函數(shù)(服務(wù)器組件)本頁(yè)將介紹如何使用這些選項(xiàng),并深入探討導(dǎo)航的工作原理。
<Link>
組件
<Link>
是一個(gè)內(nèi)置組件,它擴(kuò)展了 HTML 的 <a>
標(biāo)簽,提供了路由之間的預(yù)取和客戶端導(dǎo)航。它是 Next.js 中推薦的路由導(dǎo)航方式。
你可以從 next/link
導(dǎo)入 <Link>
,并通過(guò)傳遞 href
屬性來(lái)使用它:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">儀表盤</Link>
}
<Link>
還可以傳遞其他可選屬性,更多信息請(qǐng)參考 API 文檔。
useRouter()
鉤子
useRouter
鉤子允許你在客戶端組件中通過(guò)代碼更改路由。
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
儀表盤
</button>
)
}
如需了解 useRouter
的所有方法,請(qǐng)參考 API 文檔。
建議:除非有特定需求,否則建議使用
<Link&
組件進(jìn)行路由導(dǎo)航。
redirect
函數(shù)
對(duì)于服務(wù)器組件,應(yīng)使用 redirect
函數(shù)。
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: Promise<{ id: string }>
}) {
const { id } = await params
if (!id) {
redirect('/login')
}
const team = await fetchTeam(id)
if (!team) {
redirect('/join')
}
// ...
}
需要注意的是:
-redirect
默認(rèn)返回 307(臨時(shí)重定向)狀態(tài)碼。在服務(wù)器操作中使用時(shí),它返回 303(參見(jiàn)其他),這通常用于 POST 請(qǐng)求后的成功頁(yè)面重定向。
-redirect
內(nèi)部會(huì)拋出錯(cuò)誤,因此不應(yīng)在try/catch
塊中調(diào)用。
- 在客戶端組件的渲染過(guò)程中可以調(diào)用redirect
,但不能在事件處理程序中使用。此時(shí)可以改用useRouter
鉤子。
-redirect
也接受絕對(duì) URL,可用于重定向到外部鏈接。
- 如果希望在渲染前重定向,可以使用next.config.js
或中間件。
如需更多信息,請(qǐng)參考 redirect
API 文檔。
Next.js 允許你使用原生的 window.history.pushState
和 window.history.replaceState
方法,在不重新加載頁(yè)面的情況下更新瀏覽器歷史記錄。
pushState
和 replaceState
調(diào)用會(huì)集成到 Next.js 路由器中,使你可以與 usePathname
和 useSearchParams
同步。
window.history.pushState
使用它向?yàn)g覽器歷史記錄棧中添加新條目。用戶可以返回到之前的頁(yè)面。例如,對(duì)產(chǎn)品列表進(jìn)行排序:
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder: string) {
const params = new URLSearchParams(searchParams.toString())
params.set('sort', sortOrder)
window.history.pushState(null, '', `?${params.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>升序排序</button>
<button onClick={() => updateSorting('desc')}>降序排序</button>
</>
)
}
window.history.replaceState
使用它替換瀏覽器歷史記錄棧中的當(dāng)前條目。用戶無(wú)法返回到之前的頁(yè)面。例如,切換應(yīng)用的語(yǔ)言:
'use client'
import { usePathname } from 'next/navigation'
export function LocaleSwitcher() {
const pathname = usePathname()
function switchLocale(locale: string) {
// 例如:'/en/about' 或 '/fr/contact'
const newPath = `/${locale}${pathname}`
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>英語(yǔ)</button>
<button onClick={() => switchLocale('zh')}>中文</button>
</>
)
}
應(yīng)用路由器采用混合方式處理路由和導(dǎo)航。在服務(wù)器端,應(yīng)用代碼會(huì)根據(jù)路由段自動(dòng)進(jìn)行代碼分割。在客戶端,Next.js 會(huì)預(yù)取并緩存路由段。這意味著,當(dāng)用戶導(dǎo)航到新路由時(shí),瀏覽器不會(huì)重新加載頁(yè)面,只有更改的路由段會(huì)重新渲染,從而提升導(dǎo)航體驗(yàn)和性能。
代碼分割允許將應(yīng)用代碼拆分為更小的bundle,供瀏覽器下載和執(zhí)行。這減少了每個(gè)請(qǐng)求的數(shù)據(jù)傳輸量和執(zhí)行時(shí)間,從而提升性能。
服務(wù)器組件允許應(yīng)用代碼按路由段自動(dòng)進(jìn)行代碼分割。這意味著導(dǎo)航時(shí)只會(huì)加載當(dāng)前路由所需的代碼。
預(yù)取是在用戶訪問(wèn)頁(yè)面之前預(yù)先加載路由的一種方式。
在 Next.js 中,路由通過(guò)以下兩種方式預(yù)取:
<Link>
組件:當(dāng)路由進(jìn)入用戶視口時(shí),會(huì)自動(dòng)預(yù)取。預(yù)取在頁(yè)面首次加載或通過(guò)滾動(dòng)進(jìn)入視口時(shí)發(fā)生。router.prefetch()
:useRouter
鉤子可用于程序化預(yù)取路由。
<Link>
的默認(rèn)預(yù)取行為(即未指定或設(shè)置為 null
時(shí))取決于你對(duì) loading.js
的使用。只有共享布局,沿著渲染的“組件樹(shù)”直到第一個(gè) loading.js
文件,才會(huì)被預(yù)取并緩存30秒。這減少了獲取整個(gè)動(dòng)態(tài)路由的成本,并且你可以顯示即時(shí)加載狀態(tài),為用戶提供更好的視覺(jué)反饋。
你可以通過(guò)將 prefetch
屬性設(shè)置為 false
來(lái)禁用預(yù)取?;蛘?,你可以通過(guò)將 prefetch
屬性設(shè)置為 true
來(lái)預(yù)取加載邊界之外的完整頁(yè)面數(shù)據(jù)。
如需更多信息,請(qǐng)參考 <Link>
API 文檔。
需要注意的是:
- 預(yù)取在開(kāi)發(fā)環(huán)境中不會(huì)啟用,僅在生產(chǎn)環(huán)境中啟用。
Next.js 有一個(gè)稱為路由器緩存的內(nèi)存客戶端緩存。當(dāng)用戶在應(yīng)用中導(dǎo)航時(shí),預(yù)取的路由段和已訪問(wèn)路由的 React 服務(wù)器組件負(fù)載會(huì)存儲(chǔ)在緩存中。
這意味著在導(dǎo)航時(shí),會(huì)盡可能重復(fù)使用緩存,而不是向服務(wù)器發(fā)送新請(qǐng)求——通過(guò)減少請(qǐng)求數(shù)量和數(shù)據(jù)傳輸量來(lái)提升性能。
了解更多關(guān)于路由器緩存的工作原理以及如何配置它。
部分渲染意味著只有在導(dǎo)航時(shí)更改的路由段會(huì)在客戶端重新渲染,共享的段將被保留。
例如,在兩個(gè)兄弟路由 /dashboard/settings
和 /dashboard/analytics
之間導(dǎo)航時(shí),settings
頁(yè)面將被卸載,analytics
頁(yè)面將以前端狀態(tài)掛載,共享的 dashboard
布局將被保留。在同一個(gè)動(dòng)態(tài)段的兩個(gè)路由之間導(dǎo)航時(shí),也會(huì)出現(xiàn)這種行為。例如,使用 /blog/[slug]/page
從 /blog/first
導(dǎo)航到 /blog/second
。
沒(méi)有部分渲染時(shí),每次導(dǎo)航都會(huì)導(dǎo)致整個(gè)頁(yè)面在客戶端重新渲染。只渲染更改的段減少了數(shù)據(jù)傳輸量和執(zhí)行時(shí)間,從而提升性能。
瀏覽器在頁(yè)面之間導(dǎo)航時(shí)會(huì)執(zhí)行“硬導(dǎo)航”。Next.js 應(yīng)用路由器啟用了頁(yè)面之間的“軟導(dǎo)航”,確保只有更改的路由段會(huì)重新渲染(部分渲染)。這使得客戶端 React 狀態(tài)在導(dǎo)航期間得以保留。
默認(rèn)情況下,Next.js 將維護(hù)返回和前進(jìn)導(dǎo)航的滾動(dòng)位置,并在路由器緩存中重復(fù)使用路由段。
pages/
和 app/
之間路由
在從 pages/
逐步遷移到 app/
時(shí),Next.js 路由器將自動(dòng)處理兩者之間的硬導(dǎo)航。為了檢測(cè)從 pages/
到 app/
的過(guò)渡,有一個(gè)客戶端路由器過(guò)濾器,它利用應(yīng)用路由的概率檢查,這偶爾會(huì)導(dǎo)致誤報(bào)。默認(rèn)情況下,這種誤報(bào)的概率非常低(0.01%)。此概率可通過(guò) next.config.js
中的 experimental.clientRouterFilterAllowedRate
選項(xiàng)進(jìn)行調(diào)整。需要注意的是,降低誤報(bào)率會(huì)增加客戶端bundle中生成的過(guò)濾器的大小。
或者,如果你希望完全禁用此處理并手動(dòng)管理 pages/
和 app/
之間的路由,可以在 next.config.js
中將 experimental.clientRouterFilter
設(shè)置為 false。當(dāng)此功能被禁用時(shí),默認(rèn)情況下,pages 中的任何與 app 路由重疊的動(dòng)態(tài)路由都不會(huì)被正確導(dǎo)航到。
更多建議: