Link

<Link> es un componente de React que extiende el elemento HTML <a> para proporcionar precarga (prefetching) y navegación del lado del cliente entre rutas. Es la forma principal de navegar entre rutas en Next.js.

Uso básico:

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

Referencia

Las siguientes props pueden pasarse al componente <Link>:

PropEjemploTipoRequerido
hrefhref="/dashboard"String u Objeto
replacereplace={false}Booleano-
scrollscroll={false}Booleano-
prefetchprefetch={false}Booleano-
legacyBehaviorlegacyBehavior={true}Booleano-
passHrefpassHref={true}Booleano-
shallowshallow={false}Booleano-
localelocale="fr"String o Booleano-
onNavigateonNavigate={(e) => {}}Función-

Importante: Los atributos de la etiqueta <a> como className o target="_blank" pueden agregarse a <Link> como props y se pasarán al elemento <a> subyacente.

href (requerido)

La ruta o URL a la que navegar.

import Link from 'next/link'

// Navegar a /about?name=test
export default function Home() {
  return (
    <Link
      href={{
        pathname: '/about',
        query: { name: 'test' },
      }}
    >
      About
    </Link>
  )
}
import Link from 'next/link'

// Navegar a /about?name=test
export default function Home() {
  return (
    <Link
      href={{
        pathname: '/about',
        query: { name: 'test' },
      }}
    >
      About
    </Link>
  )
}

replace

Por defecto es false. Cuando es true, next/link reemplazará el estado actual del historial en lugar de agregar una nueva URL a la pila del historial del navegador.

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

Por defecto es true. El comportamiento de desplazamiento predeterminado de <Link> en Next.js es mantener la posición de desplazamiento, similar a cómo los navegadores manejan la navegación hacia atrás y adelante. Cuando navegas a una nueva Página, la posición de desplazamiento se mantendrá igual siempre que la Página sea visible en el viewport. Sin embargo, si la Página no es visible en el viewport, Next.js se desplazará hasta la parte superior del primer elemento de la Página.

Cuando scroll = {false}, Next.js no intentará desplazarse al primer elemento de la Página.

Importante: Next.js verifica scroll: false antes de gestionar el comportamiento de desplazamiento. Si el desplazamiento está habilitado, identifica el nodo DOM relevante para la navegación e inspecciona cada elemento de nivel superior. Se omiten todos los elementos no desplazables y aquellos sin HTML renderizado, esto incluye elementos con posición fija o sticky, y elementos no visibles como los calculados con getBoundingClientRect. Next.js continúa a través de los elementos hermanos hasta identificar un elemento desplazable que sea visible en el viewport.

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

La precarga (prefetching) ocurre cuando un componente <Link /> entra en el viewport del usuario (inicialmente o mediante desplazamiento). Next.js precarga y carga la ruta vinculada (denotada por href) y los datos en segundo plano para mejorar el rendimiento de las navegaciones del lado del cliente. La precarga solo está habilitada en producción.

Los siguientes valores pueden pasarse a la prop prefetch:

  • true (predeterminado): Se precargará toda la ruta y sus datos.
  • false: La precarga no ocurrirá al entrar en el viewport, pero sí al pasar el cursor. Si deseas eliminar completamente la precarga al pasar el cursor, considera usar una etiqueta <a> o adoptar incrementalmente el Enrutador de la Aplicación, que permite deshabilitar la precarga al pasar el cursor.
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

Advertencia: La prop legacyBehavior se eliminará en Next.js v16. Para adoptar el nuevo comportamiento de <Link>, elimina cualquier etiqueta <a> utilizada como hijo de <Link>. Hay un codemod disponible para ayudarte a actualizar automáticamente tu código base.

Desde la versión 13, un elemento <a> ya no es necesario como hijo del componente <Link>. Si aún necesitas el comportamiento anterior por razones de compatibilidad, puedes agregar la prop legacyBehavior.

Importante: cuando legacyBehavior no está establecido en true, todas las propiedades de la etiqueta anchor pueden pasarse a next/link, como className, onClick, etc.

passHref

Fuerza a Link a enviar la propiedad href a su hijo. Por defecto es false. Consulta el ejemplo de pasar un componente funcional para más información.

shallow

Actualiza la ruta de la página actual sin volver a ejecutar getStaticProps, getServerSideProps o getInitialProps. Por defecto es 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

La configuración regional activa se antepone automáticamente. locale permite proporcionar una configuración regional diferente. Cuando es false, href debe incluir la configuración regional ya que el comportamiento predeterminado está deshabilitado.

import Link from 'next/link'

export default function Home() {
  return (
    <>
      {/* Comportamiento predeterminado: se antepone la configuración regional */}
      <Link href="/dashboard">Dashboard (con configuración regional)</Link>

      {/* Deshabilitar el anteponer la configuración regional */}
      <Link href="/dashboard" locale={false}>
        Dashboard (sin configuración regional)
      </Link>

      {/* Especificar una configuración regional diferente */}
      <Link href="/dashboard" locale="fr">
        Dashboard (Francés)
      </Link>
    </>
  )
}
import Link from 'next/link'

export default function Home() {
  return (
    <>
      {/* Comportamiento predeterminado: se antepone la configuración regional */}
      <Link href="/dashboard">Dashboard (con configuración regional)</Link>

      {/* Deshabilitar el anteponer la configuración regional */}
      <Link href="/dashboard" locale={false}>
        Dashboard (sin configuración regional)
      </Link>

      {/* Especificar una configuración regional diferente */}
      <Link href="/dashboard" locale="fr">
        Dashboard (Francés)
      </Link>
    </>
  )
}

onNavigate

Un manejador de eventos que se llama durante la navegación del lado del cliente. El manejador recibe un objeto de evento que incluye un método preventDefault(), permitiéndote cancelar la navegación si es necesario.

import Link from 'next/link'

export default function Page() {
  return (
    <Link
      href="/dashboard"
      onNavigate={(e) => {
        // Solo se ejecuta durante la navegación SPA
        console.log('Navegando...')

        // Opcionalmente prevenir la navegación
        // e.preventDefault()
      }}
    >
      Dashboard
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link
      href="/dashboard"
      onNavigate={(e) => {
        // Solo se ejecuta durante la navegación SPA
        console.log('Navegando...')

        // Opcionalmente prevenir la navegación
        // e.preventDefault()
      }}
    >
      Dashboard
    </Link>
  )
}

Importante: Aunque onClick y onNavigate pueden parecer similares, sirven para diferentes propósitos. onClick se ejecuta para todos los eventos de clic, mientras que onNavigate solo se ejecuta durante la navegación del lado del cliente. Algunas diferencias clave:

  • Al usar teclas modificadoras (Ctrl/Cmd + Clic), onClick se ejecuta pero onNavigate no, ya que Next.js previene la navegación predeterminada para nuevas pestañas.
  • Las URL externas no activarán onNavigate ya que solo es para navegaciones del mismo origen y del lado del cliente.
  • Los enlaces con el atributo download funcionarán con onClick pero no con onNavigate ya que el navegador tratará la URL vinculada como una descarga.

Ejemplos

Los siguientes ejemplos demuestran cómo usar el componente <Link> en diferentes escenarios.

Enlaces a segmentos de ruta dinámicos

Para segmentos de ruta dinámicos, puede ser útil utilizar literales de plantilla para crear la ruta del enlace.

Por ejemplo, puede generar una lista de enlaces a la ruta dinámica 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

Si el hijo es un componente personalizado que envuelve una etiqueta <a>

Si el hijo de Link es un componente personalizado que envuelve una etiqueta <a>, debe agregar passHref a Link. Esto es necesario si está utilizando bibliotecas como styled-components. Sin esto, la etiqueta <a> no tendrá el atributo href, lo que perjudica la accesibilidad de su sitio y podría afectar el SEO. Si está utilizando ESLint, hay una regla incorporada next/link-passhref para garantizar el uso correcto de passHref.

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

// Esto crea un componente personalizado que envuelve una etiqueta <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'

// Esto crea un componente personalizado que envuelve una etiqueta <a>
const RedLink = styled.a`
  color: red;
`

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

export default NavLink
  • Si está utilizando la función JSX pragma de emotion (@jsx jsx), debe usar passHref incluso si utiliza una etiqueta <a> directamente.
  • El componente debe admitir la propiedad onClick para activar la navegación correctamente.

Anidar un componente funcional

Si el hijo de Link es un componente funcional, además de usar passHref y legacyBehavior, debe envolver el componente en React.forwardRef:

import Link from 'next/link'
import React from 'react'

// Definir el tipo de props para MyButton
interface MyButtonProps {
  onClick?: React.MouseEventHandler<HTMLAnchorElement>
  href?: string
}

// Usar React.ForwardRefRenderFunction para tipar correctamente la ref reenviada
const MyButton: React.ForwardRefRenderFunction<
  HTMLAnchorElement,
  MyButtonProps
> = ({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      Haz clic aquí
    </a>
  )
}

// Usar React.forwardRef para envolver el componente
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` y `ref` deben pasarse al elemento DOM
// para un manejo adecuado
const MyButton = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      Haz clic aquí
    </a>
  )
})

// Agregar un nombre de visualización para el componente (útil para depuración)
MyButton.displayName = 'MyButton'

export default function Home() {
  return (
    <Link href="/about" passHref legacyBehavior>
      <MyButton />
    </Link>
  )
}

Pasar un objeto URL

Link también puede recibir un objeto URL y lo formateará automáticamente para crear la cadena de URL:

import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link
          href={{
            pathname: '/about',
            query: { name: 'test' },
          }}
        >
          Acerca de nosotros
        </Link>
      </li>
      <li>
        <Link
          href={{
            pathname: '/blog/[slug]',
            query: { slug: 'my-post' },
          }}
        >
          Entrada de blog
        </Link>
      </li>
    </ul>
  )
}

export default Home
import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link
          href={{
            pathname: '/about',
            query: { name: 'test' },
          }}
        >
          Acerca de nosotros
        </Link>
      </li>
      <li>
        <Link
          href={{
            pathname: '/blog/[slug]',
            query: { slug: 'my-post' },
          }}
        >
          Entrada de blog
        </Link>
      </li>
    </ul>
  )
}

export default Home

El ejemplo anterior tiene un enlace a:

  • Una ruta predefinida: /about?name=test
  • Una ruta dinámica: /blog/my-post

Puede usar todas las propiedades como se definen en la documentación del módulo URL de Node.js.

Reemplazar la URL en lugar de empujar

El comportamiento predeterminado del componente Link es empujar una nueva URL a la pila del historial. Puede usar la propiedad replace para evitar agregar una nueva entrada, como en el siguiente ejemplo:

import Link from 'next/link'

export default function Home() {
  return (
    <Link href="/about" replace>
      Acerca de nosotros
    </Link>
  )
}
import Link from 'next/link'

export default function Home() {
  return (
    <Link href="/about" replace>
      Acerca de nosotros
    </Link>
  )
}

Deshabilitar el desplazamiento a la parte superior de la página

El comportamiento predeterminado de Link es desplazarse a la parte superior de la página. Cuando hay un hash definido, se desplazará al id específico, como una etiqueta <a> normal. Para evitar el desplazamiento a la parte superior / hash, se puede agregar scroll={false} a Link:

import Link from 'next/link'

export default function Home() {
  return (
    <Link href="/#hashid" scroll={false}>
      Deshabilita el desplazamiento a la parte superior
    </Link>
  )
}
import Link from 'next/link'

export default function Home() {
  return (
    <Link href="/#hashid" scroll={false}>
      Deshabilita el desplazamiento a la parte superior
    </Link>
  )
}

Prefetching de enlaces en Middleware

Es común usar Middleware para autenticación u otros propósitos que involucren reescribir al usuario a una página diferente. Para que el componente <Link /> pueda prefetchear correctamente los enlaces con reescrituras a través de Middleware, necesita indicarle a Next.js tanto la URL a mostrar como la URL a prefetchear. Esto es necesario para evitar solicitudes innecesarias al middleware para conocer la ruta correcta que debe prefetchear.

Por ejemplo, si desea servir una ruta /dashboard que tenga vistas para usuarios autenticados y visitantes, puede agregar lo siguiente en su Middleware para redirigir al usuario a la página correcta:

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

En este caso, debería usar el siguiente código en su componente <Link />:

'use client'

import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // Su hook de autenticación

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' // Su hook de autenticación

export default function Home() {
  const isAuthed = useIsAuthed()
  const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
  return (
    <Link as="/dashboard" href={path}>
      Dashboard
    </Link>
  )
}

Nota importante: Si está utilizando Rutas Dinámicas, deberá adaptar las props as y href. Por ejemplo, si tiene una Ruta Dinámica como /dashboard/authed/[user] que desea presentar de manera diferente a través de middleware, escribiría: <Link href={{ pathname: '/dashboard/authed/[user]', query: { user: username } }} as="/dashboard/[user]">Perfil</Link>.

Bloqueo de navegación

Puede usar la prop onNavigate para bloquear la navegación cuando se cumplan ciertas condiciones, como cuando un formulario tiene cambios sin guardar. Cuando necesite bloquear la navegación en múltiples componentes de su aplicación (como prevenir la navegación desde cualquier enlace mientras se edita un formulario), React Context proporciona una forma limpia de compartir este estado de bloqueo. Primero, cree un contexto para rastrear el estado de bloqueo de navegación:

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

Cree un componente de formulario que use el contexto:

'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">Guardar</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">Guardar</button>
    </form>
  )
}

Cree un componente Link personalizado que bloquee la navegación:

'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('Tiene cambios sin guardar. ¿Salir de todas formas?')
        ) {
          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('Tiene cambios sin guardar. ¿Salir de todas formas?')
        ) {
          e.preventDefault()
        }
      }}
      {...props}
    >
      {children}
    </Link>
  )
}

Cree un componente de navegación:

'use client'

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

export default function Nav() {
  return (
    <nav>
      <Link href="/">Inicio</Link>
      <Link href="/about">Acerca de</Link>
    </nav>
  )
}
'use client'

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

export default function Nav() {
  return (
    <nav>
      <Link href="/">Inicio</Link>
      <Link href="/about">Acerca de</Link>
    </nav>
  )
}

Finalmente, envuelva su aplicación con el NavigationBlockerProvider en el layout raíz y use los componentes en su página:

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

Luego, use los componentes Nav y Form en su página:

import Nav from './components/nav'
import Form from './components/form'

export default function Page() {
  return (
    <div>
      <Nav />
      <main>
        <h1>Bienvenido al Dashboard</h1>
        <Form />
      </main>
    </div>
  )
}
import Nav from './components/nav'
import Form from './components/form'

export default function Page() {
  return (
    <div>
      <Nav />
      <main>
        <h1>Bienvenido al Dashboard</h1>
        <Form />
      </main>
    </div>
  )
}

Cuando un usuario intente navegar usando CustomLink mientras el formulario tiene cambios sin guardar, se le pedirá confirmación antes de salir.

Historial de versiones

VersiónCambios
v15.3.0Se agregó la API onNavigate
v13.0.0Ya no requiere una etiqueta <a> como hijo. Se proporciona un codemod para actualizar su código automáticamente.
v10.0.0Las props href que apuntan a una ruta dinámica se resuelven automáticamente y ya no requieren una prop as.
v8.0.0Mejora en el rendimiento del prefetching.
v1.0.0Se introdujo next/link.