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:

SegmentoTipoURL resultante
tiendaEstático/tienda
productosEstá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:

  1. No son accesibles como rutas (incluso si tienen page.tsx)
  2. Útiles para organizar código compartido
  3. No afectan el sistema de routing
  4. 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:

  1. Layouts diferentes por sección:
app/
├── (auth)/                   ← Layout sin sidebar
│   ├── layout.tsx
│   ├── login/
│   └── register/
└── (dashboard)/              ← Layout con sidebar
    ├── layout.tsx
    ├── home/
    └── settings/
  1. 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:

  1. Las carpetas dentro de app/ definen segmentos de ruta
  2. Solo las carpetas con page.tsx son accesibles como rutas
  3. Usa minúsculas y guiones medios para nombres de carpetas
  4. Los corchetes [] crean segmentos dinámicos
  5. Los paréntesis () crean Route Groups que no afectan la URL
  6. El guion bajo _ crea carpetas privadas que NextJS ignora
  7. Puedes colocar archivos no relacionados con routing junto a tus rutas
  8. 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