<Link>
Navigates between routes on the client, prefetching the target route before the user clicks.
import Link from 'next/link'
<Link
href={string | UrlObject} // required
replace={boolean} // default: false
scroll={boolean} // default: true
prefetch={boolean | 'auto' | null} // default: null ("auto")
onNavigate={(event: { preventDefault(): void }) => void}
transitionTypes={string[]}
{...anchorAttributes}
>
{children}
</Link>- Added in Next.js v1.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$href | string | UrlObject | The path or URL to navigate to. An object form such as { pathname: '/about', query: { name: 'test' } } is serialised into a query string. |
$replaceoptional | boolean= false | Replaces the current entry in the browser history stack instead of pushing a new one. |
$scrolloptional | boolean= true | When false, Next.js will not scroll to the first Page element after navigation. When true it keeps the scroll position if the new Page is visible, and scrolls to the top of it if it is not. |
$prefetchoptional | boolean | "auto" | null= null | "auto" or null prefetches the full route for static routes and the partial route down to the nearest loading.js boundary for dynamic routes; true prefetches the full route in both cases; false disables prefetching on viewport entry and on hover. |
$onNavigateoptional | (event: { preventDefault(): void }) => void | Runs during client-side navigation only. Calling event.preventDefault() cancels the navigation. |
$transitionTypesoptional | string[] | Transition types passed to React.addTransitionType inside the navigation transition, so <ViewTransition> components can animate per navigation kind. |
Return value
JSX.ElementAn <a> element with the same href, wired to the App Router for client-side navigation and prefetching.
Errors
Next.js raises an invalid-prop error when href is missing or is neither a string nor a URL object.
Changelog
- PHP v16.2.0transitionTypes prop added.
- PHP v15.4.0"auto" added as an alias for the default prefetch behaviour.
- PHP v15.3.0onNavigate prop added, giving a cancellable hook into client-side navigation.
- PHP v13.0.0A child <a> tag is no longer required - <Link> renders the anchor itself. Nesting one now duplicates the element.
- PHP v10.0.0href values pointing at a dynamic route resolve automatically; the as prop is no longer required.
<Link> renders a real <a> element and layers prefetching and client-side transitions on top of it. Reach for it for every in-app route change. Keep a bare <a> only for external URLs, downloads, or a route where you deliberately want a full document load. Anchor attributes such as className, target and aria-current pass straight through to the underlying element.
What prefetching actually does
In production, a link that scrolls into the viewport triggers a background fetch of the target route. Static routes are fetched whole; dynamic routes are fetched only down to the nearest loading.js boundary. That is why a dynamic route with no loading.js can still feel slow after a prefetch — there was nothing cheap to prefetch. Prefetching is disabled entirely in next dev.
What trips people up
- Navigation does not jump to the top by default. It preserves scroll position while the new Page is visible in the viewport, and only scrolls when it is not.
onNavigateis notonClick. Cmd/Ctrl-clicks, external URLs anddownloadlinks fireonClickbut neveronNavigate.- Next.js skips sticky and fixed elements when picking the scroll target, so content can land under a sticky header. Fix it with
scroll-padding-toponhtml.
Example
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
const routes = [
{ href: '/', label: 'Home' },
{ href: '/dashboard', label: 'Dashboard' },
{ href: '/settings', label: 'Settings' },
]
export function NavLinks() {
const pathname = usePathname()
return (
<nav>
{routes.map((route) => (
<Link
key={route.href}
href={route.href}
aria-current={pathname === route.href ? 'page' : undefined}
>
{route.label}
</Link>
))}
</nav>
)
}When to use it
- Build a site navigation bar that highlights the current route with usePathname().
- Render a list of blog post links from a dynamic segment without a full page reload.
- Warn a user with unsaved form changes before letting a navigation proceed.
- Update a URL without adding a history entry by passing replace.
Frequently asked questions
Why is prefetching not working in development?
Prefetching is only enabled in production builds. Run next build && next start to observe it; next dev deliberately skips it so you are not rebuilding routes you never visit.
Do I still need an <a> tag inside <Link>?
No - since v13.0.0 <Link> renders the <a> itself, and nesting one produces a duplicate anchor. Put className, target and other anchor attributes directly on <Link>.
Why does the page not scroll to the top after navigation?
That is the documented default: <Link> preserves scroll position as long as the new Page element is visible in the viewport, mirroring browser back/forward behaviour. It only scrolls when the Page is out of view.
More examples
Linking to dynamic segments from a Server Component
A long list is exactly where the default prefetch behaviour hurts: every row that scrolls into view would fire a request. prefetch={false} turns off both viewport and hover prefetching, so the fifty links on the page cost nothing until one is clicked. Note that this file has no 'use client' directive - <Link> renders fine from a Server Component.
import Link from 'next/link'
import { getPosts } from '@/lib/posts'
export default async function BlogIndex() {
const posts = await getPosts()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`} prefetch={false}>
{post.title}
</Link>
</li>
))}
</ul>
)
}Cancelling a navigation with onNavigate
event.preventDefault() inside onNavigate stops the client-side transition and leaves the URL untouched. Because onNavigate only runs for same-origin client navigations, a Cmd-click that opens a new tab bypasses the guard entirely - the browser handles that case, not the router.
'use client'
import Link from 'next/link'
import type { ComponentProps } from 'react'
type GuardedLinkProps = ComponentProps<typeof Link> & { dirty: boolean }
export function GuardedLink({ dirty, children, ...props }: GuardedLinkProps) {
return (
<Link
{...props}
onNavigate={(event) => {
if (dirty && !window.confirm('You have unsaved changes. Leave anyway?')) {
event.preventDefault()
}
}}
>
{children}
</Link>
)
}Object href and disabling the scroll jump
The object form of href builds the query string for you and avoids hand-encoding values. The second link shows the usual reason to pass scroll={false}: the hash fragment already places the viewport, and letting the router also scroll to the Page element would undo it.
import Link from 'next/link'
export default function Page() {
return (
<>
{/* Navigates to /about?name=test */}
<Link href={{ pathname: '/about', query: { name: 'test' } }}>About</Link>
{/* Jumps to an id without Next.js also scrolling the Page element */}
<Link href="/dashboard#settings" scroll={false}>
Settings
</Link>
{/* Overwrites the current history entry */}
<Link href="/login" replace>
Sign in
</Link>
</>
)
}
Discussion