Next.js usa un sistema de enrutamiento basado en archivos donde la estructura de carpetas define automáticamente las rutas de tu aplicación.
En Next.js 13+ con App Router, cada carpeta representa un segmento de ruta y debe contener un archivo page.tsx para ser accesible:
app/
├── page.tsx # → /
├── about/
│ └── page.tsx # → /about
├── blog/
│ ├── page.tsx # → /blog
│ └── [slug]/
│ └── page.tsx # → /blog/cualquier-slug
└── dashboard/
├── layout.tsx # Layout para /dashboard/*
├── page.tsx # → /dashboard
└── settings/
└── page.tsx # → /dashboard/settings En Next.js, el sistema de rutas se basa en la estructura de carpetas dentro de app/.
Los archivos page.tsx, page.ts o layout.js se convierten automáticamente en rutas públicas, y cada carpeta representa un segmento de la URL.
Por ejemplo:
app/
├─ page.tsx → /
├─ blog/
│ ├─ page.tsx → /blog
│ └─ post/
│ └─ page.tsx → /blog/post
└─ contacto/
└─ page.tsx → /contacto Son páginas accesibles públicamente en la raíz de la aplicación, y cada carpeta representa un segmento de la URL.
Es decir, cada archivo page.tsx dentro de la carpeta app/ se convierte en una ruta accesible.
// app/page.tsx - Página principal (/)
export default function HomePage() {
return (
<div>
<h1>Página Principal</h1>
<p>Bienvenido a mi sitio web</p>
</div>
)
} // app/about/page.tsx - Página sobre nosotros (/about)
export default function AboutPage() {
return (
<div>
<h1>Sobre Nosotros</h1>
<p>Información de la empresa</p>
</div>
)
} // app/contact/page.tsx - Página de contacto (/contact)
export default function ContactPage() {
return (
<div>
<h1>Contacto</h1>
<form>
<input type="email" placeholder="Tu email" />
<textarea placeholder="Tu mensaje"></textarea>
<button type="submit">Enviar</button>
</form>
</div>
)
} Para navegar entre páginas en Next.js, se utiliza el componente <Link> de Next.js.
Este componente permite crear enlaces entre páginas de manera eficiente, aprovechando el pre-rendering y la carga diferida.
// components/Navbar.tsx
import Link from 'next/link'
export default function Navbar() {
return (
<nav className="bg-blue-600 text-white p-4">
<div className="flex space-x-4">
<Link href="/" className="hover:underline">
Inicio
</Link>
<Link href="/about" className="hover:underline">
Sobre Nosotros
</Link>
<Link href="/contact" className="hover:underline">
Contacto
</Link>
<Link href="/blog" className="hover:underline">
Blog
</Link>
</div>
</nav>
)
} Las rutas anidadas se crean con carpetas dentro de carpetas, estando cada carpeta representando un segmento de la URL.
Por ejemplo, la ruta /dashboard/analytics se crea con la siguiente estructura:
app/
└── dashboard/
├── layout.tsx # Layout compartido
├── page.tsx # /dashboard
├── analytics/
│ └── page.tsx # /dashboard/analytics
├── users/
│ ├── page.tsx # /dashboard/users
│ └── [id]/
│ └── page.tsx # /dashboard/users/123
└── settings/
├── page.tsx # /dashboard/settings
└── profile/
└── page.tsx # /dashboard/settings/profile // app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="min-h-screen bg-gray-100">
{/* Sidebar */}
<aside className="w-64 bg-white shadow-md fixed h-full">
<div className="p-4">
<h2 className="text-xl font-bold">Dashboard</h2>
<nav className="mt-4 space-y-2">
<a href="/dashboard" className="block p-2 hover:bg-gray-100">
Inicio
</a>
<a href="/dashboard/analytics" className="block p-2 hover:bg-gray-100">
Analytics
</a>
<a href="/dashboard/users" className="block p-2 hover:bg-gray-100">
Usuarios
</a>
<a href="/dashboard/settings" className="block p-2 hover:bg-gray-100">
Configuración
</a>
</nav>
</div>
</aside>
{/* Contenido principal */}
<main className="ml-64 p-8">
{children}
</main>
</div>
)
} // app/dashboard/page.tsx
export default function DashboardPage() {
return (
<div>
<h1 className="text-2xl font-bold mb-4">Dashboard Principal</h1>
<div className="grid grid-cols-3 gap-4">
<div className="bg-white p-4 rounded shadow">
<h3>Usuarios</h3>
<p className="text-2xl font-bold">1,234</p>
</div>
<div className="bg-white p-4 rounded shadow">
<h3>Ventas</h3>
<p className="text-2xl font-bold">$12,345</p>
</div>
<div className="bg-white p-4 rounded shadow">
<h3>Pedidos</h3>
<p className="text-2xl font-bold">567</p>
</div>
</div>
</div>
)
} Las rutas dinámicas usan corchetes [parametro] para capturar valores variables:
Las rutas dinámicas capturan valores variables en la URL y los pasan como parámetros a la página.
app/
└── blog/
├── page.tsx # /blog
└── [slug]/
└── page.tsx # /blog/mi-primer-post // app/blog/[slug]/page.tsx
interface Props {
params: { slug: string }
}
export default function BlogPost({ params }: Props) {
const { slug } = params
return (
<div>
<h1>Post: {slug}</h1>
<p>Contenido del post con slug: {slug}</p>
</div>
)
}
// Ejemplos de URLs:
// /blog/mi-primer-post → slug = "mi-primer-post"
// /blog/como-usar-nextjs → slug = "como-usar-nextjs" app/
└── shop/
└── [category]/
└── [product]/
└── page.tsx # /shop/electronics/laptop // app/shop/[category]/[product]/page.tsx
interface Props {
params: {
category: string
product: string
}
}
export default function ProductPage({ params }: Props) {
const { category, product } = params
return (
<div>
<nav>
<span>Tienda</span> →
<span>{category}</span> →
<span>{product}</span>
</nav>
<h1>Producto: {product}</h1>
<p>Categoría: {category}</p>
</div>
)
}
// Ejemplos:
// /shop/electronics/laptop → category="electronics", product="laptop"
// /shop/clothing/shirt → category="clothing", product="shirt" Usa [...parametro] para capturar múltiples segmentos:
app/
└── docs/
└── [...slug]/
└── page.tsx # /docs/a/b/c/d // app/docs/[...slug]/page.tsx
interface Props {
params: { slug: string[] }
}
export default function DocsPage({ params }: Props) {
const { slug } = params
return (
<div>
<h1>Documentación</h1>
<p>Ruta: /{slug.join('/')}</p>
<ul>
{slug.map((segment, index) => (
<li key={index}>Segmento {index + 1}: {segment}</li>
))}
</ul>
</div>
)
}
// Ejemplos:
// /docs/getting-started → slug = ["getting-started"]
// /docs/api/users/create → slug = ["api", "users", "create"] Las carpetas que empiezan con _ son privadas y no crean rutas:
app/
├── _components/ # No crea ruta (privada)
│ ├── Header.tsx
│ └── Footer.tsx
├── _lib/ # No crea ruta (privada)
│ └── utils.ts
├── page.tsx # → /
└── about/
└── page.tsx # → /about Las carpetas privadas son útiles para organizar componentes, utilidades y archivos que no deben ser rutas públicas.
Usa paréntesis (grupo) para organizar rutas sin afectar la URL:
app/
├── (marketing)/ # Grupo - no afecta URL
│ ├── layout.tsx # Layout para marketing
│ ├── about/
│ │ └── page.tsx # → /about
│ └── contact/
│ └── page.tsx # → /contact
├── (shop)/ # Grupo - no afecta URL
│ ├── layout.tsx # Layout para tienda
│ ├── products/
│ │ └── page.tsx # → /products
│ └── cart/
│ └── page.tsx # → /cart
└── page.tsx # → / // app/(marketing)/layout.tsx
export default function MarketingLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
{/* Header específico para marketing */}
<header className="bg-blue-600 text-white p-4">
<h1>Mi Empresa</h1>
<nav>
<a href="/about">Sobre Nosotros</a>
<a href="/contact">Contacto</a>
</nav>
</header>
<main>{children}</main>
{/* Footer específico para marketing */}
<footer className="bg-gray-800 text-white p-4">
<p>© 2024 Mi Empresa</p>
</footer>
</div>
)
} // app/(shop)/layout.tsx
export default function ShopLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
{/* Header específico para tienda */}
<header className="bg-green-600 text-white p-4">
<h1>Mi Tienda</h1>
<nav>
<a href="/products">Productos</a>
<a href="/cart">Carrito (3)</a>
</nav>
</header>
<main>{children}</main>
</div>
)
} Las rutas paralelas permiten renderizar múltiples páginas simultáneamente usando slots @nombre:
app/
└── dashboard/
├── layout.tsx
├── page.tsx
├── @analytics/ # Slot paralelo
│ └── page.tsx
├── @notifications/ # Slot paralelo
│ └── page.tsx
└── @users/ # Slot paralelo
└── page.tsx // app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
notifications,
users,
}: {
children: React.ReactNode
analytics: React.ReactNode
notifications: React.ReactNode
users: React.ReactNode
}) {
return (
<div className="grid grid-cols-2 gap-4 p-4">
{/* Contenido principal */}
<div className="col-span-2">
{children}
</div>
{/* Slots paralelos */}
<div className="bg-white p-4 rounded shadow">
<h2>Analytics</h2>
{analytics}
</div>
<div className="bg-white p-4 rounded shadow">
<h2>Notificaciones</h2>
{notifications}
</div>
<div className="col-span-2 bg-white p-4 rounded shadow">
<h2>Usuarios</h2>
{users}
</div>
</div>
)
} // app/dashboard/@analytics/page.tsx
export default function AnalyticsSlot() {
return (
<div>
<p>Visitantes hoy: 1,234</p>
<p>Páginas vistas: 5,678</p>
</div>
)
} // app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-400">404</h1>
<h2 className="text-2xl font-semibold mt-4">Página no encontrada</h2>
<p className="text-gray-600 mt-2">
La página que buscas no existe o ha sido movida.
</p>
<div className="mt-6 space-x-4">
<Link
href="/"
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
Ir al inicio
</Link>
<Link
href="/contact"
className="border border-gray-300 px-4 py-2 rounded hover:bg-gray-50"
>
Contactar soporte
</Link>
</div>
</div>
</div>
)
} // app/blog/not-found.tsx
import Link from 'next/link'
export default function BlogNotFound() {
return (
<div className="max-w-2xl mx-auto text-center py-12">
<h1 className="text-4xl font-bold text-gray-800">Post no encontrado</h1>
<p className="text-gray-600 mt-4">
El artículo que buscas no existe o ha sido eliminado.
</p>
<div className="mt-8">
<Link
href="/blog"
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700"
>
Ver todos los posts
</Link>
</div>
{/* Posts relacionados */}
<div className="mt-12">
<h3 className="text-xl font-semibold mb-4">Posts populares</h3>
<div className="space-y-2">
<Link href="/blog/como-usar-nextjs" className="block hover:underline">
Cómo usar Next.js
</Link>
<Link href="/blog/react-hooks" className="block hover:underline">
Guía de React Hooks
</Link>
</div>
</div>
</div>
)
} // app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
interface Props {
params: { slug: string }
}
// Simulamos una base de datos de posts
const posts = [
{ slug: 'como-usar-nextjs', title: 'Cómo usar Next.js' },
{ slug: 'react-hooks', title: 'Guía de React Hooks' },
]
export default function BlogPost({ params }: Props) {
const { slug } = params
// Buscar el post
const post = posts.find(p => p.slug === slug)
// Si no existe, mostrar 404
if (!post) {
notFound()
}
return (
<article>
<h1>{post.title}</h1>
<p>Contenido del post...</p>
</article>
)
} Next.js hace el enrutamiento súper simple: carpetas = rutas, page.tsx = página pública. Con rutas dinámicas, agrupadas y paralelas tienes control total sobre la estructura de tu app.
app/
├── layout.tsx # Layout raíz
├── page.tsx # → /
├── not-found.tsx # 404 global
├── (marketing)/ # Grupo marketing
│ ├── layout.tsx # Layout marketing
│ ├── about/
│ │ └── page.tsx # → /about
│ └── contact/
│ └── page.tsx # → /contact
├── (shop)/ # Grupo tienda
│ ├── layout.tsx # Layout tienda
│ ├── products/
│ │ ├── page.tsx # → /products
│ │ └── [id]/
│ │ └── page.tsx # → /products/123
│ └── cart/
│ └── page.tsx # → /cart
├── blog/
│ ├── page.tsx # → /blog
│ ├── not-found.tsx # 404 del blog
│ └── [slug]/
│ └── page.tsx # → /blog/mi-post
├── dashboard/
│ ├── layout.tsx # Layout dashboard
│ ├── page.tsx # → /dashboard
│ ├── @analytics/ # Slot paralelo
│ │ └── page.tsx
│ └── users/
│ ├── page.tsx # → /dashboard/users
│ └── [id]/
│ └── page.tsx # → /dashboard/users/123
└── _components/ # Carpeta privada
├── Header.tsx
└── Footer.tsx Con esta estructura tienes una aplicación completa con diferentes secciones, layouts específicos y manejo de errores. ¡El sistema de rutas de Next.js hace todo el trabajo pesado por ti!