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 Page() {
return <Link href="/dashboard">Dashboard</Link>
}
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
Referencia
Las siguientes props pueden pasarse al componente <Link>
:
Prop | Ejemplo | Tipo | Requerido |
---|---|---|---|
href | href="/dashboard" | String u Objeto | Sí |
replace | replace={false} | Booleano | - |
scroll | scroll={false} | Booleano | - |
prefetch | prefetch={false} | Booleano o null | - |
onNavigate | onNavigate={(e) => {}} | Función | - |
Importante: Los atributos de la etiqueta
<a>
comoclassName
otarget="_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 Page() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
import Link from 'next/link'
// Navegar a /about?name=test
export default function Page() {
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 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
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 congetBoundingClientRect
. 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 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
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 sus datos en segundo plano para mejorar el rendimiento de las navegaciones del lado del cliente. Si los datos precargados han expirado cuando el usuario pasa el cursor sobre un <Link />
, Next.js intentará precargarlos nuevamente. La precarga solo está habilitada en producción.
Los siguientes valores pueden pasarse a la prop prefetch
:
null
(predeterminado): El comportamiento de precarga depende de si la ruta es estática o dinámica. Para rutas estáticas, se precargará toda la ruta (incluyendo todos sus datos). Para rutas dinámicas, se precargará la ruta parcial hasta el segmento más cercano con un límiteloading.js
.true
: Se precargará toda la ruta tanto para rutas estáticas como dinámicas.false
: La precarga nunca ocurrirá, ni al entrar en el viewport ni al pasar el cursor.
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
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
yonNavigate
pueden parecer similares, sirven para diferentes propósitos.onClick
se ejecuta para todos los eventos de clic, mientras queonNavigate
solo se ejecuta durante la navegación del lado del cliente. Algunas diferencias clave:
- Al usar teclas modificadoras (
Ctrl
/Cmd
+ Clic),onClick
se ejecuta peroonNavigate
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 cononClick
pero no cononNavigate
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.
Enlazando a segmentos dinámicos
Al enlazar a segmentos dinámicos, puedes usar literales de plantilla e interpolación para generar una lista de enlaces. Por ejemplo, para generar una lista de publicaciones de blog:
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>
)
}
Verificación de enlaces activos
Puede utilizar usePathname()
para determinar si un enlace está activo. Por ejemplo, para agregar una clase al enlace activo, puede verificar si el pathname
actual coincide con el href
del enlace:
Desplazamiento a un id
Si desea desplazarse a un id
específico durante la navegación, puede agregar un enlace hash #
a su URL o simplemente pasar un enlace hash a la propiedad href
. Esto es posible porque <Link>
se renderiza como un elemento <a>
.
<Link href="/dashboard#settings">Configuración</Link>
// Salida
<a href="/dashboard#settings">Configuración</a>
Nota importante:
- Next.js se desplazará a la Página si no está visible en el viewport durante la navegación.
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 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>
)
}
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
.
- Si está utilizando la función JSX pragma de emotion (
@jsx jsx
), debe usarpassHref
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 Page() {
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 Page() {
return (
<Link href="/about" passHref legacyBehavior>
<MyButton />
</Link>
)
}
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 Page() {
return (
<Link href="/about" replace>
Acerca de nosotros
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/about" replace>
Acerca de nosotros
</Link>
)
}
Deshabilitar el desplazamiento a la parte superior de la página
El comportamiento predeterminado de desplazamiento de <Link>
en Next.js es mantener la posición del desplazamiento, similar a cómo los navegadores manejan la navegación hacia atrás y adelante. Cuando navega a una nueva Página, la posición del 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á a la parte superior del primer elemento de la Página. Si desea deshabilitar este comportamiento, puede pasar scroll={false}
al componente <Link>
, o scroll: false
a router.push()
o router.replace()
.
import Link from 'next/link'
export default function Page() {
return (
<Link href="/#hashid" scroll={false}>
Deshabilita el desplazamiento a la parte superior
</Link>
)
}
import Link from 'next/link'
export default function Page() {
return (
<Link href="/#hashid" scroll={false}>
Deshabilita el desplazamiento a la parte superior
</Link>
)
}
Usando router.push()
o router.replace()
:
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
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 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' // Su hook de autenticación
export default function Page() {
const isAuthed = useIsAuthed()
const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
return (
<Link as="/dashboard" href={path}>
Dashboard
</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:
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:
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ón | Cambios |
---|---|
v15.3.0 | Se agregó la API onNavigate |
v13.0.0 | Ya no requiere una etiqueta <a> como hijo. Se proporciona un codemod para actualizar su código automáticamente. |
v10.0.0 | Las props href que apuntan a una ruta dinámica se resuelven automáticamente y ya no requieren una prop as . |
v8.0.0 | Mejora en el rendimiento del prefetching. |
v1.0.0 | Se introdujo next/link . |