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> принимает следующие пропсы:

ПропсПримерТипОбязателен
hrefhref="/dashboard"String или ObjectДа
replacereplace={false}Boolean-
scrollscroll={false}Boolean-
prefetchprefetch={false}Boolean или null-
onNavigateonNavigate={(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 ссылки:

'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>
  )
}
'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

Если вы хотите прокрутить страницу к определенному 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.

import Link from 'next/link'
import styled from 'styled-components'

// Создаем кастомный компонент, оборачивающий <a>
const RedLink = styled.a`
  color: red;
`

function NavLink({ href, name }) {
  return (
    <Link href={href} passHref legacyBehavior>
      <RedLink>{name}</RedLink>
    </Link>
  )
}

export default NavLink
import Link from 'next/link'
import styled from 'styled-components'

// Создаем кастомный компонент, оборачивающий <a>
const RedLink = styled.a`
  color: red;
`

function NavLink({ href, name }) {
  return (
    <Link href={href} passHref legacyBehavior>
      <RedLink>{name}</RedLink>
    </Link>
  )
}

export default NavLink
  • При использовании 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 { createContext, useState, useContext } from 'react'

interface NavigationBlockerContextType {
  isBlocked: boolean
  setIsBlocked: (isBlocked: boolean) => void
}

export const NavigationBlockerContext =
  createContext<NavigationBlockerContextType>({
    isBlocked: false,
    setIsBlocked: () => {},
  })

export function NavigationBlockerProvider({
  children,
}: {
  children: React.ReactNode
}) {
  const [isBlocked, setIsBlocked] = useState(false)

  return (
    <NavigationBlockerContext.Provider value={{ isBlocked, setIsBlocked }}>
      {children}
    </NavigationBlockerContext.Provider>
  )
}

export function useNavigationBlocker() {
  return useContext(NavigationBlockerContext)
}
'use client'

import { createContext, useState, useContext } from 'react'

export const NavigationBlockerContext = createContext({
  isBlocked: false,
  setIsBlocked: () => {},
})

export function NavigationBlockerProvider({ children }) {
  const [isBlocked, setIsBlocked] = useState(false)

  return (
    <NavigationBlockerContext.Provider value={{ isBlocked, setIsBlocked }}>
      {children}
    </NavigationBlockerContext.Provider>
  )
}

export function useNavigationBlocker() {
  return useContext(NavigationBlockerContext)
}

Создайте компонент формы, использующий контекст:

'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>
  )
}

Создайте компонент навигации:

'use client'

import { CustomLink as Link } from './custom-link'

export default function Nav() {
  return (
    <nav>
      <Link href="/">Главная</Link>
      <Link href="/about">О нас</Link>
    </nav>
  )
}
'use client'

import { CustomLink as Link } from './custom-link'

export default function Nav() {
  return (
    <nav>
      <Link href="/">Главная</Link>
      <Link href="/about">О нас</Link>
    </nav>
  )
}

Наконец, оберните ваше приложение 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.