Link

<Link> — это React-компонент, расширяющий HTML-элемент <a> для предоставления предварительной загрузки (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> принимает следующие пропсы:

ПропсПримерТипОбязателен
hrefhref="/dashboard"String или ObjectДа
replacereplace={false}Boolean-
scrollscroll={false}Boolean-
prefetchprefetch={false}Boolean-
legacyBehaviorlegacyBehavior={true}Boolean-
passHrefpassHref={true}Boolean-
shallowshallow={false}Boolean-
localelocale="fr"String или Boolean-
onNavigateonNavigate={(e) => {}}Function-

Полезно знать: Атрибуты тега <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. Поведение прокрутки по умолчанию для <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 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

Предварительная загрузка (prefetching) происходит, когда компонент <Link /> попадает в область видимости пользователя (изначально или при прокрутке). Next.js предварительно загружает связанный маршрут (указанный в href) и данные в фоновом режиме для улучшения производительности навигации на стороне клиента. Предварительная загрузка работает только в production-режиме.

Для пропса prefetch можно передать следующие значения:

  • true (по умолчанию): Весь маршрут и его данные будут предзагружены.
  • false: Предварительная загрузка не будет происходить при попадании в область видимости, но будет происходить при наведении. Если вы хотите полностью отключить загрузку при наведении, рассмотрите использование тега <a> или постепенное внедрение маршрутизатора App, который позволяет отключать предзагрузку при наведении.
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> удалите все теги <a>, используемые как дочерние элементы <Link>. Доступен кодмод для автоматического обновления кодовой базы.

Начиная с версии 13, элемент <a> больше не требуется как дочерний элемент компонента <Link>. Если вам всё ещё нужно старое поведение для совместимости, вы можете добавить пропс legacyBehavior.

Полезно знать: когда legacyBehavior не установлен в true, все свойства anchor тега можно передавать в next/link, такие как className, onClick и т.д.

passHref

Принудительно передаёт свойство 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 + Click), 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>, необходимо добавить 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 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>
  )
})

// Добавляем displayName для компонента (полезно при отладке)
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

В примере выше создаются ссылки на:

Можно использовать все свойства, описанные в документации Node.js URL module.

Замена URL вместо добавления

По умолчанию компонент Link добавляет новый URL в стек 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 прокручивает страницу к верху. При наличии хэша прокрутка выполняется к указанному id, как у обычного тега <a>. Чтобы отключить прокрутку к верху/хэшу, можно добавить scroll={false} в Link:

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 используется для аутентификации или других целей, связанных с перезаписью 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 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>
  )
}

Полезно знать: Если вы используете Динамические маршруты, вам нужно адаптировать пропсы as и href. Например, если у вас есть динамический маршрут /dashboard/authed/[user], который вы хотите представить иначе через middleware, вы должны написать: <Link href={{ pathname: '/dashboard/authed/[user]', query: { user: username } }} as="/dashboard/[user]">Profile</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.