Link
<Link>
— это React-компонент, расширяющий HTML-элемент <a>
для предоставления предварительной загрузки (prefetching) и навигации на стороне клиента между маршрутами. Это основной способ навигации между маршрутами в Next.js.
Базовое использование:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
Справочник
Компонент <Link>
принимает следующие пропсы:
Пропс | Пример | Тип | Обязателен |
---|---|---|---|
href | href="/dashboard" | String или Object | Да |
replace | replace={false} | Boolean | - |
scroll | scroll={false} | Boolean | - |
prefetch | prefetch={false} | Boolean или null | - |
onNavigate | onNavigate={(e) => {}} | Function | - |
Полезно знать: Атрибуты тега
<a>
, такие какclassName
илиtarget="_blank"
, можно передавать в<Link>
как пропсы, и они будут переданы базовому элементу<a>
.
href
(обязателен)
Путь или URL для навигации.
import Link from 'next/link'
// Переход на /about?name=test
export default function Page() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
import Link from 'next/link'
// Переход на /about?name=test
export default function Page() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
replace
По умолчанию false
. Когда установлено в true
, next/link
заменит текущее состояние истории вместо добавления нового URL в стек истории браузера.
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" replace>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" replace>
Dashboard
</Link>
)
}
scroll
По умолчанию true
. Поведение прокрутки по умолчанию для <Link>
в Next.js сохраняет позицию прокрутки, аналогично тому, как браузеры обрабатывают навигацию назад и вперёд. При переходе на новую Страницу (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 Page() {
return (
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
)
}
prefetch
Предварительная загрузка (prefetching) происходит, когда компонент <Link />
попадает в область видимости пользователя (изначально или при прокрутке). Next.js предварительно загружает связанный маршрут (указанный в href
) и его данные в фоновом режиме для улучшения производительности навигации на стороне клиента. Если предварительно загруженные данные устарели к моменту наведения пользователя на <Link />
, Next.js попытается загрузить их снова. Предварительная загрузка работает только в production-режиме.
Для пропса prefetch
можно передать следующие значения:
null
(по умолчанию): Поведение предварительной загрузки зависит от типа маршрута. Для статических маршрутов будет предзагружен весь маршрут (включая все данные). Для динамических маршрутов будет предзагружена часть маршрута до ближайшего сегмента с границейloading.js
.true
: Весь маршрут будет предзагружен как для статических, так и для динамических маршрутов.false
: Предварительная загрузка не будет происходить ни при попадании в область видимости, ни при наведении.
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" prefetch={false}>
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
+ Click),onClick
выполняется, ноonNavigate
нет, так как Next.js предотвращает стандартную навигацию для новых вкладок.- Внешние URL не будут вызывать
onNavigate
, так как он предназначен только для навигации на стороне клиента и в пределах одного источника.- Ссылки с атрибутом
download
будут работать сonClick
, но не сonNavigate
, так как браузер будет обрабатывать URL как загрузку.
Примеры
Следующие примеры демонстрируют использование компонента <Link>
в различных сценариях.
Ссылки на динамические сегменты
При ссылках на динамические сегменты можно использовать шаблонные литералы и интерполяцию для генерации списка ссылок. Например, для генерации списка постов блога:
import Link from 'next/link'
interface Post {
id: number
title: string
slug: string
}
export default function PostList({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
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
ссылки:
Прокрутка к id
Если вы хотите прокрутить страницу к определенному id
при навигации, можно добавить к URL хэш-ссылку с #
или передать её в проп href
. Это возможно, так как <Link>
рендерится в элемент <a>
.
<Link href="/dashboard#settings">Settings</Link>
// Output
<a href="/dashboard#settings">Settings</a>
Полезно знать:
- Next.js автоматически прокрутит к Странице (Page), если она не видна в области просмотра при навигации.
Ссылки на динамические сегменты маршрутов
Для динамических сегментов маршрутов (dynamic route segments) удобно использовать шаблонные литералы для создания пути ссылки.
Например, можно сгенерировать список ссылок на динамический маршрут app/blog/[slug]/page.js
:
import Link from 'next/link'
export default function Page({ 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'
export default function Page({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
Если дочерний элемент — это кастомный компонент, оборачивающий <a>
Если дочерний элемент Link
— это кастомный компонент, оборачивающий тег <a>
, необходимо добавить passHref
в Link
. Это особенно важно при использовании библиотек вроде styled-components. Без этого тег <a>
не получит атрибут href
, что ухудшит доступность сайта и может повлиять на SEO. При использовании ESLint есть встроенное правило next/link-passhref
для контроля правильного использования passHref
.
- При использовании emotion с JSX pragma (
@jsx jsx
) необходимо использоватьpassHref
, даже если напрямую используется тег<a>
. - Компонент должен поддерживать свойство
onClick
для корректной навигации.
Вложение функционального компонента
Если дочерний элемент Link
— это функциональный компонент, помимо использования passHref
и legacyBehavior
, необходимо обернуть компонент в React.forwardRef
:
import Link from 'next/link'
import React from 'react'
// Определяем тип пропсов для MyButton
interface MyButtonProps {
onClick?: React.MouseEventHandler<HTMLAnchorElement>
href?: string
}
// Используем React.ForwardRefRenderFunction для типизации forwarded 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 Page() {
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>
)
})
// Добавляем displayName для компонента (полезно при отладке)
MyButton.displayName = 'MyButton'
export default function Page() {
return (
<Link href="/about" passHref legacyBehavior>
<MyButton />
</Link>
)
}
Замена URL вместо добавления
По умолчанию компонент Link
добавляет новый URL в стек history
. Свойство replace
позволяет предотвратить добавление новой записи, как показано в примере:
import Link from 'next/link'
export default function Page() {
return (
<Link href="/about" replace>
About us
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/about" replace>
About us
</Link>
)
}
Отключение прокрутки к верху страницы
Поведение прокрутки <Link>
в Next.js по умолчанию сохраняет позицию прокрутки, аналогично поведению браузеров при навигации назад/вперед. При переходе на новую Страницу (Page) позиция прокрутки сохраняется, если Страница видна в области просмотра.
Однако если Страница не видна в области просмотра, Next.js прокрутит к верхнему элементу Страницы. Чтобы отключить это поведение, можно передать scroll={false}
в компонент <Link>
или scroll: false
в router.push()
или router.replace()
.
import Link from 'next/link'
export default function Page() {
return (
<Link href="/#hashid" scroll={false}>
Отключить прокрутку к верху
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/#hashid" scroll={false}>
Отключить прокрутку к верху
</Link>
)
}
Использование router.push()
или router.replace()
:
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
Предварительная загрузка ссылок в Middleware
Часто Middleware используется для аутентификации или других целей, связанных с перезаписью URL пользователя на другую страницу. Чтобы компонент <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 Page() {
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 Page() {
const isAuthed = useIsAuthed()
const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
return (
<Link as="/dashboard" href={path}>
Dashboard
</Link>
)
}
Блокировка навигации
Вы можете использовать проп onNavigate
для блокировки навигации при выполнении определенных условий, например, когда форма содержит несохраненные изменения. Когда вам нужно заблокировать навигацию в нескольких компонентах вашего приложения (например, предотвратить переход по любой ссылке во время редактирования формы), React 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">Сохранить</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">Сохранить</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('У вас есть несохраненные изменения. Все равно уйти?')
) {
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('У вас есть несохраненные изменения. Все равно уйти?')
) {
e.preventDefault()
}
}}
{...props}
>
{children}
</Link>
)
}
Создайте компонент навигации:
Наконец, оберните ваше приложение NavigationBlockerProvider
в корневом layout и используйте компоненты на вашей странице:
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>Добро пожаловать в Dashboard</h1>
<Form />
</main>
</div>
)
}
import Nav from './components/nav'
import Form from './components/form'
export default function Page() {
return (
<div>
<Nav />
<main>
<h1>Добро пожаловать в Dashboard</h1>
<Form />
</main>
</div>
)
}
Когда пользователь попытается перейти по ссылке с помощью CustomLink
, пока форма содержит несохраненные изменения, ему будет предложено подтвердить действие перед уходом.
История версий
Версия | Изменения |
---|---|
v15.3.0 | Добавлен API onNavigate |
v13.0.0 | Больше не требует дочернего тега <a> . Предоставляется codemod для автоматического обновления кодовой базы. |
v10.0.0 | Пропсы href , указывающие на динамический маршрут, автоматически разрешаются и больше не требуют пропса as . |
v8.0.0 | Улучшена производительность предварительной загрузки. |
v1.0.0 | Введен next/link . |