Una única fuente de verdad para las rutas en las aplicaciones Next.js
Aprenda cómo un módulo de rutas centralizado por escritura reemplaza las rutas de cadena dispersas en Next.js, con funciones auxiliares para segmentos dinámicos y exportaciones de rutas agrupadas.
La mayoría de los proyectos Next.js comienzan escribiendo las rutas directamente en los componentes Link y en las llamadas a router.push. Eso es rápido para las primeras páginas, pero a medida que la aplicación crece, las mismas cadenas de texto se duplican en barras de navegación, pies de página, redirecciones y correos electrónicos, y cada cambio de nombre se convierte en una tarea de búsqueda y reemplazo. El problema empeora cuando los asistentes de programación basados en IA generan parte del código, ya que rara vez siguen una convención de manera consistente. Transferir todas las rutas a un módulo escrito a mano soluciona este problema.
Qué sale mal con las rutas incrustadas
Aquí hay un componente de navegación típico donde cada destino es una cadena de texto literal:
// ❌ Problematic approach
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function Navbar() {
const router = useRouter()
const handleNavigation = () => {
router.push('/dashboard/user/profile')
}
return (
<nav>
<Link href="/about">About</Link>
<Link href="/contact">Contact</Link>
<Link href="/blog">Blog</Link>
<Link href="/dashboard/analytics">Analytics</Link>
<button onClick={handleNavigation}>Profile</button>
</nav>
)
}
Por ahora no hay nada roto, pero surgen cuatro problemas a medida que la base de código y el equipo crecen.
Cambiar nombres se convierte en una tarea complicada
Cambiar /blog por /articles implica buscar cada aparición en los componentes, el código del servidor, las pruebas y la configuración. Si se pasa por alto uno de ellos, queda un enlace roto.
Los errores tipográficos se escapan
Una ruta como /dashbord es simplemente una cadena de texto, por lo que ni el compilador ni los herramientas de análisis la detectan. El error suele manifestarse como un 404, a veces solo en producción.
El editor no puede ayudar
Dado que las rutas son cadenas de texto sin estructura, no existe autocompletado, ni opción de “ir a la definición”, ni advertencia cuando una ruta ya no existe.
Los compañeros de equipo se desvían
Diferentes personas y diferentes instrucciones a la IA terminan escribiendo la misma ruta de formas ligeramente distintas: con o sin barra final, con una cadena de consulta creada a mano, etc.
Centralizar las rutas en un único módulo
La solución consiste en un único archivo de configuración que toda la aplicación importa.
Dónde se encuentra el archivo
Un folder constants o config junto a app y components facilita su localización:
/src
/app
/components
/constants
└── routes.ts
/lib
Definición del objeto de rutas
El módulo a continuación agrupa las rutas por área. Las páginas estáticas son simples cadenas de texto. Las páginas dinámicas son funciones pequeñas que reciben el segmento variable y devuelven la ruta completa, de modo que quienes las llaman nunca tengan que construir URLs a mano. Los puntos finales de API se encuentran bajo una clave anidada API para su uso en llamadas fetch. La expresión final as const permite a TypeScript inferir los valores literales exactos y marca el objeto como de solo lectura, lo que facilita el autocompletado preciso y evita reasignaciones accidentales.
// constants/routes.ts
export const ROUTES = {
// Public routes
HOME: '/',
ABOUT: '/about',
CONTACT: '/contact',
PRICING: '/pricing',
// Auth routes
LOGIN: '/auth/login',
REGISTER: '/auth/register',
FORGOT_PASSWORD: '/auth/forgot-password',
// Blog routes
BLOG: '/blog',
BLOG_DETAIL: (slug: string) => `/blog/${slug}`,
BLOG_CATEGORY: (category: string) => `/blog/category/${category}`,
// Dashboard routes
DASHBOARD: '/dashboard',
DASHBOARD_ANALYTICS: '/dashboard/analytics',
DASHBOARD_SETTINGS: '/dashboard/settings',
DASHBOARD_PROFILE: '/dashboard/profile',
// Dynamic user routes
USER_PROFILE: (userId: string) => `/user/${userId}`,
USER_POSTS: (userId: string) => `/user/${userId}/posts`,
// API routes (useful for fetch calls)
API: {
USERS: '/api/users',
POSTS: '/api/posts',
AUTH: '/api/auth',
},
} as const
// Export route groups for better organization
export const AUTH_ROUTES = {
LOGIN: ROUTES.LOGIN,
REGISTER: ROUTES.REGISTER,
FORGOT_PASSWORD: ROUTES.FORGOT_PASSWORD,
} as const
export const DASHBOARD_ROUTES = {
HOME: ROUTES.DASHBOARD,
ANALYTICS: ROUTES.DASHBOARD_ANALYTICS,
SETTINGS: ROUTES.DASHBOARD_SETTINGS,
PROFILE: ROUTES.DASHBOARD_PROFILE,
} as const
Las dos exportaciones adicionales, AUTH_ROUTES y DASHBOARD_ROUTES, son vistas del objeto principal destinadas al código que solo se ocupa de un área específica, como el middleware que protege el panel de control. Dado que hacen referencia a ROUTES en lugar de repetir cadenas de texto, sigue habiendo una sola definición por ruta.
Una mejora que vale la pena considerar: si un slug o ID puede contener espacios o caracteres especiales, envuélvalo en encodeURIComponent dentro de la función auxiliar para que cada llamante reciba automáticamente una URL segura.
Uso de las rutas en componentes
El navbar ahora importa nombres en lugar de cadenas de texto. Tanto Link como la navegación imperativa leen del mismo módulo, por lo que nunca pueden entrar en conflicto:
// components/Navbar.tsx
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ROUTES, DASHBOARD_ROUTES } from '@/constants/routes'
export default function Navbar() {
const router = useRouter()
const handleProfileNavigation = () => {
router.push(DASHBOARD_ROUTES.PROFILE)
}
return (
<nav className="flex space-x-4">
<Link href={ROUTES.ABOUT} className="hover:text-blue-500">
About
</Link>
<Link href={ROUTES.CONTACT} className="hover:text-blue-500">
Contact
</Link>
<Link href={ROUTES.BLOG} className="hover:text-blue-500">
Blog
</Link>
<Link href={DASHBOARD_ROUTES.ANALYTICS} className="hover:text-blue-500">
Analytics
</Link>
<button
onClick={handleProfileNavigation}
className="rounded bg-blue-500 px-4 py-2 text-white"
>
Profile
</button>
</nav>
)
}
Tenga en cuenta que este ejemplo importa useRouter de next/router, el cual pertenece al Pages Router. En proyectos de App Router, hay que importarlo de next/navigation dentro de un componente cliente; el módulo de rutas funciona de la misma manera con cualquiera de ellos.
Creación de enlaces dinámicos
Los segmentos dinámicos son donde este patrón resulta más útil. La lista a continuación enlaza cada publicación y su categoría al llamar a las funciones auxiliares con los datos de la publicación:
// components/BlogList.tsx
import Link from 'next/link'
import { ROUTES } from '@/constants/routes'
interface Post {
slug: string
title: string
category: string
}
interface BlogListProps {
posts: Post[]
}
export default function BlogList({ posts }: BlogListProps) {
return (
<div className="space-y-4">
{posts.map((post) => (
<article key={post.slug} className="rounded border p-4">
<h2 className="text-xl font-bold">
<Link href={ROUTES.BLOG_DETAIL(post.slug)} className="hover:text-blue-500">
{post.title}
</Link>
</h2>
<Link
href={ROUTES.BLOG_CATEGORY(post.category)}
className="text-sm text-gray-500 hover:text-blue-500"
>
Category: {post.category}
</Link>
</article>
))}
</div>
)
}
Si la estructura de la URL de las publicaciones cambia alguna vez, por ejemplo al agregar un segmento de año, solo debe actualizar BLOG_DETAIL una vez y todos los lugares que lo usen seguirán esa actualización. TypeScript también marcará cualquier llamada que envíe un número incorrecto de argumentos.
Beneficios
- Cada ruta se define una sola vez y se reutiliza en todas partes.
Límites y alternativas
Un archivo de constantes no verifica si realmente existe una página correspondiente en el directorio app o pages. Si se renombra una carpeta sin actualizar el módulo, los enlaces siguen rompiéndose. Next.js también ofrece una opción de rutas tipadas que genera tipos a partir del sistema de archivos; consulte la documentación actual para conocer su estado y si se adapta a su configuración. Muchos equipos combinan ambos enfoques: tipos generados para garantizar la corrección, y un módulo de constantes para nombres legibles y funciones auxiliares.
Cuando los asistentes de IA contribuyan código, indíqueles en las instrucciones de su proyecto que importen desde el módulo de rutas.
Conclusión
Trate las URLs como parte de la API de su aplicación y no como cadenas dispersas. Un módulo de rutas tipado y centralizado convierte los renombramientos en una sola edición, muestra los errores en el editor y proporciona a personas y herramientas un lugar evidente donde buscar.
Lecturas relacionadas
- Migrar de next/router: Params, URLs Shallow y 404s en App Router — Descubre cómo funcionan los params, useParams, las actualizaciones de URLs Shallow, la navegación programática y las páginas 404 reales en el App Router de Next.js una vez que next/router ya no esté disponible.
- ¿Acciones en el servidor o manejadores de ruta? Una guía de toma de decisiones para Next.js 16 — Aprende cuándo una mutación en Next.js 16 debe ir en una Acción en el servidor y cuándo necesita un Manejador de ruta, con ejemplos corregidos para formularios, errores, enfoque optimista y webhooks.