Next.js proporciona herramientas poderosas para manejar la navegación de manera eficiente, optimizando automáticamente la carga de páginas y mejorando la experiencia del usuario.
El componente Link es la forma principal de navegar entre páginas en Next.js, proporcionando navegación del lado del cliente.
// components/Navigation.tsx
import Link from 'next/link'
export function Navigation() {
return (
<nav className="navigation">
<Link href="/">
Inicio
</Link>
<Link href="/about">
Acerca de
</Link>
<Link href="/products">
Productos
</Link>
<Link href="/contact">
Contacto
</Link>
</nav>
)
} // components/ProductList.tsx
import Link from 'next/link'
interface Product {
id: string
name: string
slug: string
}
export function ProductList({ products }: { products: Product[] }) {
return (
<div className="product-list">
{products.map((product) => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
{/* Ruta dinámica con parámetros */}
<Link href={`/products/${product.slug}`}>
Ver detalles
</Link>
{/* Usando objeto href */}
<Link
href={{
pathname: '/products/[slug]',
query: { slug: product.slug }
}}
>
Ver con objeto
</Link>
</div>
))}
</div>
)
} El Router Client es una poderosa herramienta para manejar la navegación programática en Next.js.
// app/layout.tsx
'use client'
import { useRouter, usePathname } from 'next/navigation'
import { useEffect } from 'react'
export function ClientNavigation() {
const router = useRouter()
const pathname = usePathname()
// Navegación programática
const handleNavigation = (path: string) => {
router.push(path)
}
// Detectar cambios de ruta
useEffect(() => {
console.log('Ruta actual:', pathname)
}, [pathname])
return (
<div className="client-nav">
<button onClick={() => handleNavigation('/dashboard')}>
Ir al Dashboard
</button>
<button onClick={() => router.back()}>
Volver
</button>
<button onClick={() => router.forward()}>
Adelante
</button>
<button onClick={() => router.refresh()}>
Refrescar
</button>
</div>
)
} El atributo prefetch permite pre-cargar la página destino, mejorando la experiencia del usuario. El atributo replace reemplaza la entrada actual en el historial de navegación, evitando que el usuario regrese a la página anterior.
// components/AdvancedLink.tsx
import Link from 'next/link'
export function AdvancedLink() {
return (
<div className="advanced-links">
{/* Prefetch deshabilitado */}
<Link href="/heavy-page" prefetch={false}>
Página pesada (sin prefetch)
</Link>
{/* Replace en lugar de push */}
<Link href="/login" replace>
Login (reemplaza historial)
</Link>
{/* Scroll deshabilitado */}
<Link href="/section#content" scroll={false}>
Ir a sección (sin scroll)
</Link>
{/* Link externo */}
<Link
href="https://nextjs.org"
target="_blank"
rel="noopener noreferrer"
>
Sitio externo
</Link>
</div>
)
} El componente ActiveLink permite agregar una clase activa cuando la ruta coincida con la href.
// components/ActiveLink.tsx
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { ReactNode } from 'react'
interface ActiveLinkProps {
href: string
children: ReactNode
activeClassName?: string
exactMatch?: boolean
}
export function ActiveLink({
href,
children,
activeClassName = 'active',
exactMatch = false
}: ActiveLinkProps) {
const pathname = usePathname()
const isActive = exactMatch
? pathname === href
: pathname.startsWith(href)
return (
<Link
href={href}
className={`nav-link ${isActive ? activeClassName : ''}`}
>
{children}
</Link>
)
}
// Uso del componente
export function MainNavigation() {
return (
<nav>
<ActiveLink href="/" exactMatch>
Inicio
</ActiveLink>
<ActiveLink href="/blog">
Blog
</ActiveLink>
<ActiveLink href="/products">
Productos
</ActiveLink>
</nav>
)
} Next.js permite manejar parámetros de búsqueda en las rutas dinámicas. El componente SearchNavigation muestra cómo navegar entre diferentes categorías y cómo realizar búsquedas dinámicas.
// components/SearchNavigation.tsx
'use client'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
export function SearchNavigation() {
const router = useRouter()
const searchParams = useSearchParams()
const [query, setQuery] = useState('')
const handleSearch = () => {
const params = new URLSearchParams(searchParams)
if (query) {
params.set('q', query)
} else {
params.delete('q')
}
router.push(`/search?${params.toString()}`)
}
return (
<div className="search-nav">
{/* Links con query params */}
<Link href="/products?category=electronics">
Electrónicos
</Link>
<Link href="/products?category=clothing&sort=price">
Ropa por precio
</Link>
{/* Búsqueda dinámica */}
<div className="search-form">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Buscar productos..."
/>
<button onClick={handleSearch}>
Buscar
</button>
</div>
</div>
)
} Los hooks de Next.js, como useRouter y useSearchParams, permiten manejar la navegación y los parámetros de búsqueda de manera programática.
// hooks/useNavigation.ts
'use client'
import { useRouter } from 'next/navigation'
import { useTransition } from 'react'
export function useNavigation() {
const router = useRouter()
const [isPending, startTransition] = useTransition()
const navigateTo = (path: string, options?: {
replace?: boolean
scroll?: boolean
}) => {
startTransition(() => {
if (options?.replace) {
router.replace(path, { scroll: options.scroll })
} else {
router.push(path, { scroll: options.scroll })
}
})
}
const goBack = () => {
startTransition(() => {
router.back()
})
}
const refresh = () => {
startTransition(() => {
router.refresh()
})
}
return {
navigateTo,
goBack,
refresh,
isPending
}
} Next.js proporciona los hooks usePathname y useSearchParams para acceder a la información de la ruta y los parámetros de búsqueda. El componente RouteInfo muestra cómo utilizar estos hooks para mostrar la información de la ruta actual y sus parámetros.
// components/RouteInfo.tsx
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
export function RouteInfo() {
const pathname = usePathname()
const searchParams = useSearchParams()
const [routeData, setRouteData] = useState({
path: '',
params: {} as Record<string, string>
})
useEffect(() => {
const params: Record<string, string> = {}
searchParams.forEach((value, key) => {
params[key] = value
})
setRouteData({
path: pathname,
params
})
}, [pathname, searchParams])
return (
<div className="route-info">
<h3>Información de la Ruta</h3>
<p><strong>Pathname:</strong> {routeData.path}</p>
{Object.keys(routeData.params).length > 0 && (
<div>
<strong>Parámetros:</strong>
<ul>
{Object.entries(routeData.params).map(([key, value]) => (
<li key={key}>
{key}: {value}
</li>
))}
</ul>
</div>
)}
</div>
)
} Next.js permite crear hooks personalizados para manejar la navegación y la activación de rutas. El hook useActiveRoute muestra cómo detectar la ruta activa y generar breadcrumbs basados en la ruta actual.
// hooks/useActiveRoute.ts
'use client'
import { usePathname } from 'next/navigation'
import { useMemo } from 'react'
interface RouteConfig {
path: string
label: string
icon?: string
children?: RouteConfig[]
}
export function useActiveRoute(routes: RouteConfig[]) {
const pathname = usePathname()
const activeRoute = useMemo(() => {
const findActiveRoute = (routeList: RouteConfig[]): RouteConfig | null => {
for (const route of routeList) {
if (pathname === route.path) {
return route
}
if (route.children) {
const childMatch = findActiveRoute(route.children)
if (childMatch) return childMatch
}
if (pathname.startsWith(route.path + '/')) {
return route
}
}
return null
}
return findActiveRoute(routes)
}, [pathname, routes])
const breadcrumbs = useMemo(() => {
const segments = pathname.split('/').filter(Boolean)
return segments.map((segment, index) => ({
label: segment.charAt(0).toUpperCase() + segment.slice(1),
path: '/' + segments.slice(0, index + 1).join('/')
}))
}, [pathname])
return {
activeRoute,
breadcrumbs,
isActive: (path: string) => pathname === path,
isParentActive: (path: string) => pathname.startsWith(path)
}
} // components/OptimizedNavigation.tsx
import Link from 'next/link'
export function OptimizedNavigation() {
return (
<nav className="optimized-nav">
{/*
Prefetch automático en producción
Solo para rutas internas
Se ejecuta cuando el Link entra en viewport
*/}
<Link href="/dashboard">
Dashboard (prefetch automático)
</Link>
{/*
Prefetch manual para control granular
*/}
<Link href="/analytics" prefetch={true}>
Analytics (prefetch forzado)
</Link>
{/*
Sin prefetch para rutas pesadas
*/}
<Link href="/reports" prefetch={false}>
Reportes (sin prefetch)
</Link>
</nav>
)
} Next.js permite interceptar rutas y crear layouts paralelos para mejorar la experiencia de usuario. El ejemplo @modal/(.)photo/[id]/page.tsx muestra cómo interceptar una ruta para mostrar un modal con detalles de una foto. El componente PhotoGallery muestra cómo renderizar una galería de fotos con enlaces a sus detalles.
// app/@modal/(.)photo/[id]/page.tsx
// Intercepting route para modal
import { Modal } from '@/components/Modal'
import { PhotoDetail } from '@/components/PhotoDetail'
export default function PhotoModal({
params
}: {
params: { id: string }
}) {
return (
<Modal>
<PhotoDetail id={params.id} />
</Modal>
)
}
// components/PhotoGallery.tsx
import Link from 'next/link'
export function PhotoGallery({ photos }: { photos: Photo[] }) {
return (
<div className="photo-gallery">
{photos.map((photo) => (
<Link
key={photo.id}
href={`/photo/${photo.id}`}
className="photo-item"
>
<img src={photo.thumbnail} alt={photo.title} />
</Link>
))}
</div>
)
} Next.js permite crear middleware para manejar la navegación y las rutas. El middleware middleware.ts muestra cómo redirigir rutas antiguas, proteger rutas privadas y reescribir URLs.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
// Redirección condicional
if (pathname.startsWith('/old-blog')) {
return NextResponse.redirect(
new URL('/blog', request.url)
)
}
// Proteger rutas privadas
if (pathname.startsWith('/dashboard')) {
const token = request.cookies.get('auth-token')
if (!token) {
return NextResponse.redirect(
new URL('/login', request.url)
)
}
}
// Reescritura de URL
if (pathname.startsWith('/api/v1')) {
return NextResponse.rewrite(
new URL(`/api${pathname.slice(6)}`, request.url)
)
}
return NextResponse.next()
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
} Next.js permite crear routers personalizados para manejar la navegación y las transiciones. El ejemplo components/AdvancedRouter.tsx muestra cómo usar useRouter y useTransition para manejar la navegación con estado y transiciones.
// components/AdvancedRouter.tsx
'use client'
import { useRouter } from 'next/navigation'
import { useTransition, useState } from 'react'
export function AdvancedRouter() {
const router = useRouter()
const [isPending, startTransition] = useTransition()
const [navigationState, setNavigationState] = useState<{
from: string | null
to: string | null
isNavigating: boolean
}>({
from: null,
to: null,
isNavigating: false
})
const navigateWithState = (path: string) => {
setNavigationState({
from: window.location.pathname,
to: path,
isNavigating: true
})
startTransition(() => {
router.push(path)
// Simular finalización de navegación
setTimeout(() => {
setNavigationState(prev => ({
...prev,
isNavigating: false
}))
}, 100)
})
}
return (
<div className="advanced-router">
{navigationState.isNavigating && (
<div className="navigation-loader">
Navegando de {navigationState.from} a {navigationState.to}...
</div>
)}
<button
onClick={() => navigateWithState('/products')}
disabled={isPending}
>
{isPending ? 'Navegando...' : 'Ir a Productos'}
</button>
<button
onClick={() => navigateWithState('/services')}
disabled={isPending}
>
{isPending ? 'Navegando...' : 'Ir a Servicios'}
</button>
</div>
)
} Next.js permite crear hooks personalizados para manejar la navegación con confirmación. El hook useConfirmNavigation muestra cómo mostrar un modal de confirmación antes de navegar a una nueva ruta.
// hooks/useConfirmNavigation.ts
'use client'
import { useRouter } from 'next/navigation'
import { useCallback, useState } from 'react'
export function useConfirmNavigation() {
const router = useRouter()
const [showConfirm, setShowConfirm] = useState(false)
const [pendingNavigation, setPendingNavigation] = useState<string | null>(null)
const navigateWithConfirm = useCallback((
path: string,
message: string = '¿Estás seguro de que quieres salir?'
) => {
if (window.confirm(message)) {
router.push(path)
}
}, [router])
const navigateWithModal = useCallback((path: string) => {
setPendingNavigation(path)
setShowConfirm(true)
}, [])
const confirmNavigation = useCallback(() => {
if (pendingNavigation) {
router.push(pendingNavigation)
setPendingNavigation(null)
}
setShowConfirm(false)
}, [router, pendingNavigation])
const cancelNavigation = useCallback(() => {
setPendingNavigation(null)
setShowConfirm(false)
}, [])
return {
navigateWithConfirm,
navigateWithModal,
confirmNavigation,
cancelNavigation,
showConfirm,
pendingNavigation
}
} Usa prefetch={false} para rutas pesadas y replace para navegación que no debe agregarse al historial. El prefetch automático mejora la UX significativamente.
Siempre usa el componente Link para navegación interna. Los enlaces externos deben incluir target="_blank" y rel="noopener noreferrer" por seguridad.
Implementa estados de carga durante navegación y asegúrate de que los enlaces tengan texto descriptivo. Usa aria-current="page" para enlaces activos.
Combina useTransition con navegación para mejor UX. Los hooks usePathname y useSearchParams son esenciales para componentes que dependen de la ruta actual.