Definiendo Rutas
En NextJS 15, las rutas se definen mediante la estructura de carpetas dentro del directorio app/
. Esta guía explica en detalle cómo crear rutas correctamente, qué convenciones seguir y cómo organizar tu aplicación.
Anatomía de una ruta
Una ruta en NextJS está compuesta por segmentos de ruta. Cada segmento corresponde a una carpeta en tu estructura de archivos:
app/tienda/productos/[id]/page.tsx
Esta estructura se divide en:
Segmento | Tipo | URL resultante |
---|---|---|
tienda | Estático | /tienda |
productos | Estático | /tienda/productos |
[id] | Dinámico | /tienda/productos/123 |
Ruta completa: /tienda/productos/123
Cada carpeta es un segmento de la URL final.
Convenciones de nomenclatura
Nombres de carpetas
Las carpetas que definen tus rutas deben seguir estas reglas:
Caracteres permitidos:
- Letras minúsculas:
a-z
- Números:
0-9
- Guion medio:
mi-ruta
- Guion bajo (underscore):
mi_ruta
Evita usar:
- Espacios:
mi ruta
- Mayúsculas:
MiRuta
(funcionan pero no es recomendado) - Caracteres especiales:
mi@ruta
,mi#ruta
- Puntos:
mi.ruta
(puede causar problemas)
app/
├── productos/ ✓ Correcto
├── mis-productos/ ✓ Correcto (preferido)
├── mis_productos/ ✓ Funciona pero no recomendado
├── Productos/ △ Funciona pero evítalo
├── mis productos/ ✗ No funcionará
└── mis.productos/ ✗ Puede causar problemas
Convención recomendada
Usa siempre minúsculas con guiones medios para separar palabras: carrito-de-compras
, historial-pedidos
, datos-usuario
. Esto es más legible y es el estándar en la comunidad.
Sensibilidad a mayúsculas
En desarrollo local (Windows, macOS), las rutas NO son sensibles a mayúsculas. Pero en producción (servidores Linux), SÍ lo son:
Desarrollo:
/Productos === /productos === /PRODUCTOS
Producción:
/Productos ≠ /productos ≠ /PRODUCTOS
Siempre usa minúsculas para evitar problemas al deployar.
Segmentos de ruta
Segmentos estáticos
Son carpetas con nombres fijos que definen rutas exactas:
app/
├── tienda/
│ └── page.tsx → /tienda
├── contacto/
│ └── page.tsx → /contacto
└── sobre-nosotros/
└── page.tsx → /sobre-nosotros
Segmentos dinámicos
Usan corchetes []
para crear rutas que aceptan valores variables:
app/
└── productos/
└── [id]/
└── page.tsx → /productos/123
→ /productos/abc
→ /productos/cualquier-valor
El valor entre corchetes se convierte en un parámetro:
// app/productos/[id]/page.tsx
export default function ProductoPage({
params,
}: {
params: { id: string }
}) {
// params.id contiene el valor de la URL
return <h1>Producto ID: {params.id}</h1>
}
Los segmentos dinámicos se explican a fondo en Dynamic Routes. Esta sección solo cubre lo básico para entender cómo se definen.
Segmentos catch-all (atrapar todo)
Usan corchetes dobles [[...]]
para capturar múltiples segmentos:
app/
└── docs/
└── [...slug]/
└── page.tsx → /docs/a
→ /docs/a/b
→ /docs/a/b/c
// app/docs/[...slug]/page.tsx
export default function DocsPage({
params,
}: {
params: { slug: string[] }
}) {
// params.slug es un array
// /docs/guias/nextjs → slug = ['guias', 'nextjs']
return <h1>Documentación: {params.slug.join('/')}</h1>
}
Variante opcional: [[...slug]]
con corchetes dobles también captura la ruta base:
app/
└── tienda/
└── [[...categoria]]/
└── page.tsx → /tienda (slug = undefined)
→ /tienda/ropa (slug = ['ropa'])
→ /tienda/ropa/hombre (slug = ['ropa', 'hombre'])
Archivos que definen rutas
Solo ciertos archivos hacen que una carpeta sea accesible como ruta:
page.tsx - Hace la ruta pública
Sin page.tsx
, la carpeta NO es accesible:
app/
├── productos/ ← /productos no existe (no hay page.tsx)
│ └── [id]/
│ └── page.tsx ← /productos/123 sí existe
└── tienda/
└── page.tsx ← /tienda sí existe
Resultado:
/tienda
→ funciona/productos
→ 404 (no hay page.tsx)/productos/123
→ funciona
Error común
Crear una carpeta sin page.tsx
no crea una ruta. Si accedes a esa URL, obtendrás un error 404.
Archivos que NO crean rutas
Estos archivos tienen funciones especiales pero no hacen que la carpeta sea accesible:
app/
└── dashboard/
├── layout.tsx ← Proporciona UI compartida
├── loading.tsx ← Muestra estado de carga
├── error.tsx ← Maneja errores
├── not-found.tsx ← Página 404 personalizada
└── template.tsx ← Similar a layout pero no persistente
Sin un page.tsx
, /dashboard
no es accesible aunque tenga estos archivos.
Colocation - Archivos junto a rutas
Puedes colocar archivos que NO son rutas dentro de app/
:
app/
└── productos/
├── page.tsx ← Ruta: /productos
├── components/
│ └── ProductCard.tsx ← Componente (no es ruta)
├── utils/
│ └── formatPrice.ts ← Utilidad (no es ruta)
└── styles.module.css ← Estilos (no es ruta)
NextJS solo trata como rutas las carpetas que contienen archivos especiales de routing (page.tsx
, etc.).
Esta característica se llama colocation (colocación en español) y te permite organizar archivos relacionados cerca de las rutas que los usan.
Ejemplo real - E-commerce
app/
└── tienda/
├── page.tsx ← /tienda
├── layout.tsx ← Layout para toda la tienda
├── components/
│ ├── ProductGrid.tsx ← Componente compartido
│ ├── FilterSidebar.tsx ← Componente compartido
│ └── SortDropdown.tsx ← Componente compartido
├── productos/
│ ├── page.tsx ← /tienda/productos
│ ├── [slug]/
│ │ ├── page.tsx ← /tienda/productos/camisa-azul
│ │ ├── components/
│ │ │ ├── ProductImages.tsx ← Componente específico
│ │ │ ├── AddToCart.tsx ← Componente específico
│ │ │ └── RelatedProducts.tsx ← Componente específico
│ │ └── actions.ts ← Server Actions
│ └── actions/
│ └── getProducts.ts ← Data fetching
└── carrito/
├── page.tsx ← /tienda/carrito
└── components/
└── CartItem.tsx ← Componente específico
Solo las carpetas con page.tsx
crean rutas públicas. Todo lo demás es organización interna.
Carpetas privadas
Usa guion bajo (underscore, el símbolo _
) al inicio del nombre para crear carpetas privadas que NextJS ignora completamente:
app/
├── _lib/ ← Carpeta privada
│ ├── database.ts
│ └── utils.ts
├── _components/ ← Carpeta privada
│ ├── Header.tsx
│ └── Footer.tsx
└── productos/
└── page.tsx ← /productos
Características de carpetas privadas:
- No son accesibles como rutas (incluso si tienen
page.tsx
) - Útiles para organizar código compartido
- No afectan el sistema de routing
- Puedes anidarlas dentro de rutas normales
app/
└── dashboard/
├── page.tsx ← /dashboard
├── _components/ ← Privada (componentes internos)
│ └── Sidebar.tsx
└── analytics/
├── page.tsx ← /dashboard/analytics
└── _utils/ ← Privada (utilidades internas)
└── calculations.ts
¿Cuándo usar carpetas privadas?
Úsalas cuando quieras organizar archivos dentro de app/
pero no quieres arriesgarte a que NextJS los trate como rutas en el futuro. Es una forma explícita de decir "esto no es una ruta".
Route Groups - Organización sin URL
Los Route Groups usan paréntesis ()
para organizar rutas sin afectar la URL:
app/
├── (marketing)/
│ ├── layout.tsx ← Layout solo para marketing
│ ├── sobre-nosotros/
│ │ └── page.tsx → /sobre-nosotros
│ ├── contacto/
│ │ └── page.tsx → /contacto
│ └── blog/
│ └── page.tsx → /blog
└── (tienda)/
├── layout.tsx ← Layout solo para tienda
├── productos/
│ └── page.tsx → /productos
└── carrito/
└── page.tsx → /carrito
Los nombres (marketing)
y (tienda)
NO aparecen en la URL. Son solo para organización y para aplicar layouts diferentes.
Casos de uso:
- Layouts diferentes por sección:
app/
├── (auth)/ ← Layout sin sidebar
│ ├── layout.tsx
│ ├── login/
│ └── register/
└── (dashboard)/ ← Layout con sidebar
├── layout.tsx
├── home/
└── settings/
- Organización lógica:
app/
├── (publico)/
│ ├── home/
│ └── about/
└── (privado)/
├── dashboard/
└── profile/
Los Route Groups se explican en detalle en Route Groups. Aquí solo cubrimos cómo afectan la definición de rutas.
Jerarquía de archivos especiales
NextJS busca estos archivos en cada segmento de ruta (en este orden):
app/segmento/
├── layout.tsx 1. Envuelve todo (persistente)
├── template.tsx 2. Similar a layout (no persistente)
├── error.tsx 3. Maneja errores
├── loading.tsx 4. Muestra mientras carga
├── not-found.tsx 5. 404 personalizado
└── page.tsx 6. Contenido de la ruta
Cómo se anidan:
layout.tsx
└─ template.tsx
└─ error.tsx
└─ loading.tsx (mientras carga)
└─ page.tsx
Si hay un error, error.tsx
lo captura. Si la ruta no existe, not-found.tsx
se muestra.
Casos especiales y edge cases
Ruta raíz vs otras rutas
app/
├── page.tsx ← / (raíz)
└── home/
└── page.tsx ← /home (diferente)
La raíz /
y /home
son rutas distintas. No redirigen automáticamente una a la otra.
Múltiples page.tsx en la misma ruta
app/
└── productos/
├── page.tsx ✓ Correcto
└── page.js ✗ Error: conflicto
Solo puede haber UN archivo page
por carpeta (ya sea .tsx
, .ts
, .jsx
, o .js
).
Carpetas vacías
app/
└── productos/ ← Carpeta vacía
Las carpetas vacías se ignoran. No crean rutas ni causan errores.
Archivos sueltos en app/
app/
├── page.tsx ✓ Ruta: /
├── utils.ts ✓ Ignorado (no es archivo especial)
└── constants.ts ✓ Ignorado (no es archivo especial)
Los archivos que no son page.tsx
, layout.tsx
, etc., se ignoran y no afectan el routing.
Segmentos con puntos
app/
└── api.v1/ △ Funciona pero evítalo
└── page.tsx → /api.v1
Técnicamente funciona pero puede causar confusión. Mejor usa guiones: api-v1
.
Mejores prácticas
1. Nomenclatura consistente
✓ Buenos nombres:
- productos
- detalle-producto
- historial-pedidos
- configuracion-usuario
✗ Evita:
- Productos (mayúscula)
- detalle_producto (underscore)
- historial.pedidos (punto)
- configuración-usuario (tilde)
2. Estructura plana cuando sea posible
✗ Demasiado anidado:
app/tienda/categoria/productos/item/detalle/page.tsx
→ /tienda/categoria/productos/item/detalle
✓ Más plano:
app/tienda/producto/[id]/page.tsx
→ /tienda/producto/123
URLs más cortas son mejores para SEO y UX.
3. Agrupa archivos relacionados
✓ Organizado:
app/
└── productos/
├── page.tsx
├── layout.tsx
├── loading.tsx
├── error.tsx
├── components/
├── utils/
└── [id]/
└── page.tsx
✗ Desorganizado:
app/
├── productos/
│ └── page.tsx
└── producto-details/
└── page.tsx ← Debería estar en productos/[id]
4. Usa Route Groups para layouts diferentes
app/
├── (marketing)/
│ ├── layout.tsx ← Layout con header/footer
│ └── home/
├── (app)/
│ ├── layout.tsx ← Layout con sidebar
│ └── dashboard/
└── (auth)/
├── layout.tsx ← Layout simple
└── login/
5. Carpetas privadas para código compartido
app/
├── _lib/ ← Funciones compartidas
├── _components/ ← Componentes globales
├── _hooks/ ← Custom hooks
└── dashboard/
└── page.tsx
6. Nombres descriptivos
✓ Descriptivos:
- detalle-producto
- historial-compras
- configuracion-perfil
✗ Genéricos:
- item
- details
- page
Ejemplo completo - E-commerce
Estructura completa de una aplicación de e-commerce:
app/
├── layout.tsx ← Layout global
├── page.tsx ← / (home)
├── not-found.tsx ← 404 global
│
├── _lib/ ← Carpeta privada
│ ├── db.ts
│ └── utils.ts
│
├── _components/ ← Componentes globales
│ ├── Header.tsx
│ └── Footer.tsx
│
├── (marketing)/ ← Route group
│ ├── layout.tsx ← Layout con header/footer
│ ├── sobre-nosotros/
│ │ └── page.tsx → /sobre-nosotros
│ ├── contacto/
│ │ └── page.tsx → /contacto
│ └── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/mi-articulo
│
├── (tienda)/ ← Route group
│ ├── layout.tsx ← Layout de tienda
│ ├── productos/
│ │ ├── page.tsx → /productos
│ │ ├── loading.tsx
│ │ ├── components/
│ │ │ ├── ProductCard.tsx
│ │ │ └── FilterBar.tsx
│ │ └── [slug]/
│ │ ├── page.tsx → /productos/camisa-azul
│ │ ├── loading.tsx
│ │ ├── error.tsx
│ │ └── components/
│ │ ├── ProductGallery.tsx
│ │ └── AddToCart.tsx
│ │
│ ├── categorias/
│ │ └── [slug]/
│ │ └── page.tsx → /categorias/ropa
│ │
│ ├── carrito/
│ │ ├── page.tsx → /carrito
│ │ └── components/
│ │ └── CartItem.tsx
│ │
│ └── checkout/
│ ├── page.tsx → /checkout
│ ├── layout.tsx
│ └── confirmacion/
│ └── page.tsx → /checkout/confirmacion
│
└── (cuenta)/ ← Route group
├── layout.tsx ← Layout de cuenta
├── perfil/
│ └── page.tsx → /perfil
├── pedidos/
│ ├── page.tsx → /pedidos
│ └── [id]/
│ └── page.tsx → /pedidos/123
└── configuracion/
└── page.tsx → /configuracion
Estructura de URLs resultante:
Rutas públicas:
/
/sobre-nosotros
/contacto
/blog
/blog/mi-articulo
Rutas de tienda:
/productos
/productos/camisa-azul
/categorias/ropa
/carrito
/checkout
/checkout/confirmacion
Rutas de cuenta:
/perfil
/pedidos
/pedidos/123
/configuracion
Los Route Groups (marketing)
, (tienda)
, y (cuenta)
no aparecen en las URLs pero permiten aplicar layouts diferentes a cada sección.
Resumen
Puntos clave para definir rutas:
- Las carpetas dentro de
app/
definen segmentos de ruta - Solo las carpetas con
page.tsx
son accesibles como rutas - Usa minúsculas y guiones medios para nombres de carpetas
- Los corchetes
[]
crean segmentos dinámicos - Los paréntesis
()
crean Route Groups que no afectan la URL - El guion bajo
_
crea carpetas privadas que NextJS ignora - Puedes colocar archivos no relacionados con routing junto a tus rutas
- Los archivos especiales (
layout
,loading
,error
) no crean rutas por sí mismos
Convenciones importantes:
- Nombres de archivo:
page.tsx
,layout.tsx
,loading.tsx
,error.tsx
,not-found.tsx
- Un solo
page.tsx
por carpeta - Las rutas son case-sensitive en producción
- URLs más cortas son mejores