Inicio / Artículos / Una única fuente de verdad para las rutas en las aplicaciones Next.js

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.

1222 palabras

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.
  • Los nombres de rutas escritos incorrectamente generan errores en tiempo de compilación en lugar de producir errores 404.
  • Un cambio en la URL se realiza con una sola edición.
  • La estructura permanece organizada a medida que aumenta el número de páginas.
  • Los editores ofrecen completado automático y verificación de escritura para cada destino.
  • Las mismas constantes se utilizan en barras de navegación, pies de página, mapas del sitio y pistas de navegación.
  • Un único archivo proporciona un resumen rápido de todas las páginas de la aplicación.
  • 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