Mejores prácticas de enrutamiento en Next.js
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
- Espera params y searchParams: En Next.js 15+ ambas props son Promises en Server Components —
const { id } = await params— la desestructuración sinawaitdevuelve el objeto Promise y tus búsquedas silenciosamente devuelvenundefined. - No dejes que page.tsx colisione con route.tsx: Una carpeta no puede contener tanto
page.tsxcomoroute.tsx— el Route Handler sombreará la página, así que mantén las rutas API bajoapp/api/para evitar conflictos silenciosos. - Usa template.tsx para reiniciar por cada navegación:
layout.tsxpersiste a través de la navegación y su estado sobrevive a los cambios de ruta hija; cuando específicamente necesitas remontar (p. ej., reiniciar animaciones o estado en cada navegación), usatemplate.tsxen su lugar. - Analiza los tipos de parámetros dinámicos: Todo valor de segmento dinámico llega como una cadena —
const postId = Number(params.id)— así que convierte a la forma que realmente necesitas en lugar de confiar solo en la anotación de tipo. - Usa [[...slug]] cuando la ruta base debe coincidir: Un catch-all
[...path]no coincide con la ruta padre, así que/docses 404; cambia a[[...path]]o añade unpage.tsxseparado junto a él cuando el segmento base debe resolver. - Deja que lo estático gane sobre lo dinámico explícitamente: Cuando las rutas estáticas y dinámicas se superponen, la estática siempre gana, así que puedes mantener un
/blog/about/page.tsxjunto a[slug]/page.tsxsin conflicto. - generateStaticParams se ejecuta en tiempo de compilación: La función se ejecuta durante
next build, así que cualquier fuente de datos no disponible falla la compilación; combínala condynamicParams = truecuando las nuevas rutas aún deben renderizarse en la primera solicitud. - error.tsx debe ser "use client": React Error Boundaries requieren ciclo de vida del cliente, así que cada
error.tsxnecesita la directiva"use client"— y solo captura errores debajo de sí, nunca en ellayout.tsxhermano del mismo segmento. - global-error.tsx controla html y body:
global-error.tsxreemplaza el layout raíz completo cuando se activa, así que debe renderizar su propio<html><body>; también ten en cuenta que solo se activa en producción (la superposición de desarrollo se ejecuta en desarrollo). - notFound y redirect lanzan excepciones: Ambos lanzan errores centinela de Next.js, así que un
try/catchcircundante los traga — llámalos fuera detry/catch, o usaunstable_rethrowen el bloque catch para dejarlos propagarse. - Reserva root not-found para rutas no coincidentes: El root
not-found.tsxcaptura automáticamente cualquier URL que no coincida con una ruta, mientras que los archivosnot-found.tsxanidados solo se activan cuando explícitamente llamas anotFound()desde dentro de ese subárbol. - Los layouts no pueden leer searchParams: Solo las pages reciben
searchParams; si un layout necesita estado de URL, levanta la lógica a una page o un Client Component que usauseSearchParams()dentro de un límite Suspense. - Pasa datos vía Context, no props de layout: Los layouts no pueden reenviar props a sus hijos, así que comparte estado a través de un proveedor Client Context, cookies, o un auxiliar Server Component que vuelve a obtener en cada hijo.
- Importa useRouter desde next/navigation: En el App Router,
useRouter,usePathname, yuseSearchParamsviven ennext/navigation;next/routeres el Pages Router y silenciosamente falla o es 404 cuando se importa. - Envuelve useSearchParams en Suspense:
useSearchParams()sin un límite<Suspense>circundante opta toda la ruta al renderizado del lado del cliente — siempre delimítalo:<Suspense fallback={null}><SearchFilters /></Suspense>. - Usa useTransition para UI pendiente:
router.push()devuelvevoid, así que envuelve navegaciones enuseTransition—startTransition(() => router.push("/dashboard"))— para obtener una banderaisPendingy deshabilitar botones o mostrar spinners durante cambios de ruta. - router.refresh con alcance a la ruta actual:
refresh()vuelve a obtener datos del servidor para la ruta activa solo; otras rutas cacheadas permanecen obsoletas, así que combínala conrevalidateTag/revalidatePathcuando la mutación afecta hermanos. - Controla Link prefetch y scroll:
<Link>prefetcha en la entrada del viewport y se desplaza hacia arriba por defecto —<Link href="/settings" prefetch={false} scroll={false}>— estableceprefetch={false}en enlaces raramente usados yscroll={false}para UIs de pestaña/filtro que deben permanecer en su lugar. - Siempre establece un matcher de middleware: Sin
config.matchertu middleware se ejecuta en cada solicitud incluyendo/_next/staticy favicons —export const config = { matcher: ["/dashboard/:path*", "/api/:path*"] }— define el alcance explícitamente y excluye destinos de redirección para evitar bucles infinitos. - Edge middleware prohíbe APIs Node: Middleware se ejecuta en Edge Runtime, así que
bcrypt,fs, y la mayoría de drivers de base de datos se rompen en compilación o runtime — usa solo APIs Web y librerías compatibles con edge. - Devuelve NextResponse.next para continuar:
NextResponse.next()no hace corto-circuito; debesreturn(o una redirección/reescritura) para realmente detener la cadena de middleware, de lo contrario la lógica subsecuente sigue ejecutándose. - Cada slot paralelo necesita default.tsx: Las carpetas
@slotdeben incluir un fallbackdefault.tsx, de lo contrario navegaciones duras o subrutas no coincidentes son 404; los slots también deben ser hijos directos del layout y no pueden anidar dentro uno del otro. - Reserva espacio para slots independientes: Los slots de ruta paralela se resuelven independientemente, así que da a cada uno una altura fija o skeleton que coincida con el contenido final — de lo contrario streaming causa desplazamiento de layout visible mientras cada slot se rellena.
- Ten en mente la convención del punto de intercepción:
(.),(..),(..)(..), y(...)cuentan los segmentos de grupos de ruta igual que las carpetas regulares, así que los grupos de ruta pueden requerir un salto(..)extra; la falta dedefault.tsxen el slot anfitrión también rompe el cierre de back-navigation. - Los grupos de ruta no pueden colisionar: Dos carpetas
(group)no pueden resolver a la misma ruta de URL (la compilación falla), y múltiples layouts raíz fuerzan una recarga de página completa entre grupos en lugar de navegación suave — mantén la ruta del inicio/en exactamente un grupo.