Link
<Link>
是一個擴展 HTML <a>
元素的 React 組件,提供 預取 (prefetching) 和路由間的客戶端導航功能。它是 Next.js 中在路由之間導航的主要方式。
基本用法:
import Link from 'next/link'
export default function Home() {
return <Link href="/dashboard">Dashboard</Link>
}
import Link from 'next/link'
export default function Home() {
return <Link href="/dashboard">Dashboard</Link>
}
參考說明
以下屬性可以傳遞給 <Link>
組件:
屬性 | 範例 | 類型 | 必填 |
---|---|---|---|
href | href="/dashboard" | 字串或物件 | 是 |
replace | replace={false} | 布林值 | - |
scroll | scroll={false} | 布林值 | - |
prefetch | prefetch={false} | 布林值 | - |
legacyBehavior | legacyBehavior={true} | 布林值 | - |
passHref | passHref={true} | 布林值 | - |
shallow | shallow={false} | 布林值 | - |
locale | locale="fr" | 字串或布林值 | - |
onNavigate | onNavigate={(e) => {}} | 函式 | - |
須知:
<a>
標籤的屬性如className
或target="_blank"
可以作為屬性添加到<Link>
中,並將傳遞給底層的<a>
元素。
href
(必填)
要導航至的路徑或 URL。
import Link from 'next/link'
// 導航至 /about?name=test
export default function Home() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
import Link from 'next/link'
// 導航至 /about?name=test
export default function Home() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
replace
預設為 false
。 當設為 true
時,next/link
將替換當前的歷史狀態,而不是在 瀏覽器的歷史記錄 堆疊中添加新的 URL。
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" replace>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" replace>
Dashboard
</Link>
)
}
scroll
預設為 true
。 Next.js 中 <Link>
的預設滾動行為是 保持滾動位置,類似於瀏覽器處理前後導航的方式。當您導航到新的 頁面 (Page) 時,只要該頁面在視口中可見,滾動位置將保持不變。但是,如果頁面不在視口中,Next.js 將滾動到第一個頁面元素的頂部。
當 scroll = {false}
時,Next.js 不會嘗試滾動到第一個頁面元素。
須知:Next.js 在管理滾動行為之前會檢查
scroll: false
。如果滾動啟用,它會識別導航的相關 DOM 節點並檢查每個頂層元素。所有不可滾動的元素和那些沒有渲染 HTML 的元素都會被跳過,這包括固定定位或 sticky 定位的元素,以及不可見的元素(例如通過getBoundingClientRect
計算的元素)。Next.js 然後繼續檢查兄弟元素,直到找到一個在視口中可見的可滾動元素。
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
)
}
prefetch
當 <Link />
組件進入用戶的視口(初始或通過滾動)時,預取 (prefetching) 會發生。Next.js 在後台預取並加載連結的路由(由 href
表示)和數據,以提高客戶端導航的性能。預取僅在生產環境中啟用。
可以傳遞給 prefetch
屬性的值如下:
true
(預設):將預取完整路由及其數據。false
:進入視口時不會發生預取,但懸停時會發生。如果您想完全移除懸停時的預取,可以考慮使用<a>
標籤或 逐步採用 應用路由器,它也允許禁用懸停時的預取。
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
)
}
legacyBehavior
警告:
legacyBehavior
屬性將在 Next.js v16 中移除。要採用新的<Link>
行為,請移除任何用作<Link>
子元素的<a>
標籤。提供了一個 代碼修改工具 (codemod) 幫助您自動升級代碼庫。
自版本 13 起,<a>
元素不再是 <Link>
組件的必需子元素。如果您仍然需要舊行為以保持兼容性,可以添加 legacyBehavior
屬性。
須知:當
legacyBehavior
未設為true
時,所有anchor
標籤屬性都可以傳遞給next/link
,例如className
、onClick
等。
passHref
強制 Link
將 href
屬性傳遞給其子元素。預設為 false
。有關更多信息,請參閱 傳遞函式組件 範例。
shallow
更新當前頁面的路徑而不重新運行 getStaticProps
、getServerSideProps
或 getInitialProps
。預設為 false
。
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" shallow={false}>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Home() {
return (
<Link href="/dashboard" shallow={false}>
Dashboard
</Link>
)
}
locale
活動語言環境會自動前置。locale
允許提供不同的語言環境。當設為 false
時,href
必須包含語言環境,因為預設行為被禁用。
import Link from 'next/link'
export default function Home() {
return (
<>
{/* 預設行為:語言環境被前置 */}
<Link href="/dashboard">Dashboard (包含語言環境)</Link>
{/* 禁用語言環境前置 */}
<Link href="/dashboard" locale={false}>
Dashboard (不包含語言環境)
</Link>
{/* 指定不同的語言環境 */}
<Link href="/dashboard" locale="fr">
Dashboard (法文)
</Link>
</>
)
}
import Link from 'next/link'
export default function Home() {
return (
<>
{/* 預設行為:語言環境被前置 */}
<Link href="/dashboard">Dashboard (包含語言環境)</Link>
{/* 禁用語言環境前置 */}
<Link href="/dashboard" locale={false}>
Dashboard (不包含語言環境)
</Link>
{/* 指定不同的語言環境 */}
<Link href="/dashboard" locale="fr">
Dashboard (法文)
</Link>
</>
)
}
onNavigate
在客戶端導航期間調用的事件處理程序。處理程序接收一個包含 preventDefault()
方法的事件對象,允許您在需要時取消導航。
import Link from 'next/link'
export default function Page() {
return (
<Link
href="/dashboard"
onNavigate={(e) => {
// 僅在 SPA 導航期間執行
console.log('導航中...')
// 可選擇阻止導航
// e.preventDefault()
}}
>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link
href="/dashboard"
onNavigate={(e) => {
// 僅在 SPA 導航期間執行
console.log('導航中...')
// 可選擇阻止導航
// e.preventDefault()
}}
>
Dashboard
</Link>
)
}
須知:雖然
onClick
和onNavigate
看起來相似,但它們有不同的用途。onClick
執行所有點擊事件,而onNavigate
僅在客戶端導航期間運行。一些關鍵區別:
- 當使用修飾鍵(
Ctrl
/Cmd
+ 點擊)時,onClick
會執行但onNavigate
不會,因為 Next.js 阻止了新標籤的預設導航。- 外部 URL 不會觸發
onNavigate
,因為它僅用於客戶端和同源導航。- 帶有
download
屬性的連結將與onClick
一起工作,但不會與onNavigate
一起工作,因為瀏覽器會將連結的 URL 視為下載。
範例
以下範例演示了如何在不同情境下使用 <Link>
組件。
連結至動態路由區段
對於 動態路由區段 (dynamic route segments),使用模板字面值來建立連結路徑會非常方便。
例如,你可以生成一系列連結到動態路由 pages/blog/[slug].js
import Link from 'next/link'
function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
import Link from 'next/link'
function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
export default Posts
當子元素是包裹 <a>
標籤的自訂元件時
若 Link
的子元素是包裹 <a>
標籤的自訂元件,你必須在 Link
上添加 passHref
屬性。這在使用如 styled-components 等函式庫時是必要的。若缺少此屬性,<a>
標籤將不會有 href
屬性,這會影響網站的無障礙性 (accessibility) 並可能影響 SEO。若你使用 ESLint,內建的 next/link-passhref
規則可確保正確使用 passHref
。
- 若你使用 emotion 的 JSX pragma 功能 (
@jsx jsx
),即使直接使用<a>
標籤也必須添加passHref
。 - 元件應支援
onClick
屬性以正確觸發導航。
嵌套函式元件
若 Link
的子元素是函式元件,除了使用 passHref
和 legacyBehavior
外,還必須使用 React.forwardRef
包裹元件:
import Link from 'next/link'
import React from 'react'
// 定義 MyButton 的 props 類型
interface MyButtonProps {
onClick?: React.MouseEventHandler<HTMLAnchorElement>
href?: string
}
// 使用 React.ForwardRefRenderFunction 正確類型化轉發的 ref
const MyButton: React.ForwardRefRenderFunction<
HTMLAnchorElement,
MyButtonProps
> = ({ onClick, href }, ref) => {
return (
<a href={href} onClick={onClick} ref={ref}>
Click Me
</a>
)
}
// 使用 React.forwardRef 包裹元件
const ForwardedMyButton = React.forwardRef(MyButton)
export default function Home() {
return (
<Link href="/about" passHref legacyBehavior>
<ForwardedMyButton />
</Link>
)
}
import Link from 'next/link'
import React from 'react'
// `onClick`、`href` 和 `ref` 需要傳遞給 DOM 元素以正確處理
const MyButton = React.forwardRef(({ onClick, href }, ref) => {
return (
<a href={href} onClick={onClick} ref={ref}>
Click Me
</a>
)
})
// 為元件添加顯示名稱 (便於除錯)
MyButton.displayName = 'MyButton'
export default function Home() {
return (
<Link href="/about" passHref legacyBehavior>
<MyButton />
</Link>
)
}
傳遞 URL 物件
Link
也可以接收 URL 物件,它會自動格式化為 URL 字串:
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About us
</Link>
</li>
<li>
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: 'my-post' },
}}
>
Blog Post
</Link>
</li>
</ul>
)
}
export default Home
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About us
</Link>
</li>
<li>
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: 'my-post' },
}}
>
Blog Post
</Link>
</li>
</ul>
)
}
export default Home
上述範例包含以下連結:
- 預定義路由:
/about?name=test
- 動態路由 (dynamic route):
/blog/my-post
你可以使用 Node.js URL 模組文件 中定義的所有屬性。
替換 URL 而非推送
Link
元件的預設行為是將新 URL push
到 history
堆疊中。你可以使用 replace
屬性來避免新增記錄,如下例所示:
import Link from 'next/link'
export default function Home() {
return (
<Link href="/about" replace>
About us
</Link>
)
}
import Link from 'next/link'
export default function Home() {
return (
<Link href="/about" replace>
About us
</Link>
)
}
禁用滾動至頁面頂部
Link
的預設行為是滾動至頁面頂部。當定義了 hash 時,它會滾動至特定 id,就像一般的 <a>
標籤。要防止滾動至頂部 / hash,可以在 Link
上添加 scroll={false}
:
import Link from 'next/link'
export default function Home() {
return (
<Link href="/#hashid" scroll={false}>
禁用滾動至頂部
</Link>
)
}
import Link from 'next/link'
export default function Home() {
return (
<Link href="/#hashid" scroll={false}>
禁用滾動至頂部
</Link>
)
}
在 Middleware 中預取連結
常見的做法是使用 Middleware 來處理身份驗證或其他需要將使用者重新導向至不同頁面的情況。為了讓 <Link />
元件能正確預取透過 Middleware 重寫的連結,您需要告訴 Next.js 要顯示的 URL 和要預取的 URL。這樣可以避免為了知道要預取的正確路由而向 middleware 發出不必要的請求。
例如,如果您想提供一個具有已驗證和訪客視圖的 /dashboard
路由,可以在 Middleware 中加入以下程式碼來將使用者重定向到正確的頁面:
import { NextResponse } from 'next/server'
export function middleware(request: Request) {
const nextUrl = request.nextUrl
if (nextUrl.pathname === '/dashboard') {
if (request.cookies.authToken) {
return NextResponse.rewrite(new URL('/auth/dashboard', request.url))
} else {
return NextResponse.rewrite(new URL('/public/dashboard', request.url))
}
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
const nextUrl = request.nextUrl
if (nextUrl.pathname === '/dashboard') {
if (request.cookies.authToken) {
return NextResponse.rewrite(new URL('/auth/dashboard', request.url))
} else {
return NextResponse.rewrite(new URL('/public/dashboard', request.url))
}
}
}
在這種情況下,您需要在 <Link />
元件中使用以下程式碼:
'use client'
import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // 您的驗證鉤子
export default function Home() {
const isAuthed = useIsAuthed()
const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
return (
<Link as="/dashboard" href={path}>
Dashboard
</Link>
)
}
'use client'
import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // 您的驗證鉤子
export default function Home() {
const isAuthed = useIsAuthed()
const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
return (
<Link as="/dashboard" href={path}>
Dashboard
</Link>
)
}
須知:如果您使用 動態路由 (Dynamic Routes),則需要調整
as
和href
屬性。例如,如果您有一個動態路由如/dashboard/authed/[user]
,並想透過 middleware 以不同方式呈現,您可以這樣寫:<Link href={{ pathname: '/dashboard/authed/[user]', query: { user: username } }} as="/dashboard/[user]">Profile</Link>
。
阻止導航
您可以使用 onNavigate
屬性來在滿足某些條件時阻止導航,例如當表單有未儲存的變更時。當您需要在應用程式的多個元件中阻止導航(例如在編輯表單時阻止從任何連結導航),React Context 提供了一種乾淨的方式來共享這個阻止狀態。首先,建立一個 context 來追蹤導航阻止狀態:
建立一個使用 context 的表單元件:
'use client'
import { useNavigationBlocker } from '../contexts/navigation-blocker'
export default function Form() {
const { setIsBlocked } = useNavigationBlocker()
return (
<form
onSubmit={(e) => {
e.preventDefault()
setIsBlocked(false)
}}
onChange={() => setIsBlocked(true)}
>
<input type="text" name="name" />
<button type="submit">Save</button>
</form>
)
}
'use client'
import { useNavigationBlocker } from '../contexts/navigation-blocker'
export default function Form() {
const { setIsBlocked } = useNavigationBlocker()
return (
<form
onSubmit={(e) => {
e.preventDefault()
setIsBlocked(false)
}}
onChange={() => setIsBlocked(true)}
>
<input type="text" name="name" />
<button type="submit">Save</button>
</form>
)
}
建立一個自訂的 Link 元件來阻止導航:
'use client'
import Link from 'next/link'
import { useNavigationBlocker } from '../contexts/navigation-blocker'
interface CustomLinkProps extends React.ComponentProps<typeof Link> {
children: React.ReactNode
}
export function CustomLink({ children, ...props }: CustomLinkProps) {
const { isBlocked } = useNavigationBlocker()
return (
<Link
onNavigate={(e) => {
if (
isBlocked &&
!window.confirm('You have unsaved changes. Leave anyway?')
) {
e.preventDefault()
}
}}
{...props}
>
{children}
</Link>
)
}
'use client'
import Link from 'next/link'
import { useNavigationBlocker } from '../contexts/navigation-blocker'
export function CustomLink({ children, ...props }) {
const { isBlocked } = useNavigationBlocker()
return (
<Link
onNavigate={(e) => {
if (
isBlocked &&
!window.confirm('You have unsaved changes. Leave anyway?')
) {
e.preventDefault()
}
}}
{...props}
>
{children}
</Link>
)
}
建立一個導航元件:
最後,在根佈局中用 NavigationBlockerProvider
包裹您的應用程式,並在頁面中使用這些元件:
import { NavigationBlockerProvider } from './contexts/navigation-blocker'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<NavigationBlockerProvider>{children}</NavigationBlockerProvider>
</body>
</html>
)
}
import { NavigationBlockerProvider } from './contexts/navigation-blocker'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<NavigationBlockerProvider>{children}</NavigationBlockerProvider>
</body>
</html>
)
}
然後,在您的頁面中使用 Nav
和 Form
元件:
import Nav from './components/nav'
import Form from './components/form'
export default function Page() {
return (
<div>
<Nav />
<main>
<h1>Welcome to the Dashboard</h1>
<Form />
</main>
</div>
)
}
import Nav from './components/nav'
import Form from './components/form'
export default function Page() {
return (
<div>
<Nav />
<main>
<h1>Welcome to the Dashboard</h1>
<Form />
</main>
</div>
)
}
當使用者嘗試在表單有未儲存的變更時使用 CustomLink
導航離開時,系統會提示他們確認是否要離開。
版本歷史
版本 | 變更 |
---|---|
v15.3.0 | 新增 onNavigate API |
v13.0.0 | 不再需要子 <a> 標籤。提供了一個 codemod 來自動更新您的程式碼庫。 |
v10.0.0 | 指向動態路由的 href 屬性會自動解析,不再需要 as 屬性。 |
v8.0.0 | 改進了預取效能。 |
v1.0.0 | 引入 next/link 。 |