Skill de profundización en el routing de Next.js - Una receta de skill para Claude Code sobre patrones avanzados de routing en App Router
Estas recetas de skills están diseñadas para Claude Code, pero también funcionan con otros agentes de codificación con IA que admitan archivos de skill/instrucciones.
Receta
El contenido completo de SKILL.md que puedes copiar en .claude/skills/nextjs-routing/SKILL.md:
---
name: nextjs-routing-deep-dive
description: "Patrones avanzados de routing en App Router, rutas paralelas, rutas interceptadas y layouts. Úsalo cuando te pidan: ayuda con routing, rutas paralelas, rutas interceptadas, patrón modal, grupos de rutas, rutas dinámicas, middleware, rutas catch-all."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
# Profundización en el routing de Next.js
Eres un experto en routing del App Router de Next.js. Proporciona orientación autorizada sobre cada patrón de routing disponible en App Router.
## Convenciones de archivos
| Archivo | Propósito | Renderiza |
|------|---------|---------|
| layout.tsx | Contenedor de UI compartido; preserva el state en la navegación | Envuelve children |
| template.tsx | Como layout, pero se vuelve a montar en cada navegación | Envuelve children |
| page.tsx | UI única de la ruta; hace la ruta accesible públicamente | Contenido de la ruta |
| loading.tsx | UI de carga instantánea mediante Suspense | Mientras la página carga |
| error.tsx | Error boundary del segmento | Ante un error |
| not-found.tsx | UI 404 del segmento | Al llamar notFound() |
| default.tsx | Alternativa para slots de rutas paralelas sin coincidencia | Fallback del slot |
| route.ts | Endpoint de API (GET, POST, etc.) | JSON o Response |
| global-error.tsx | Error boundary a nivel raíz | Ante un error raíz |
## Tipos de rutas
### Rutas estáticasapp/about/page.tsx -> /about app/blog/page.tsx -> /blog app/dashboard/settings/page.tsx -> /dashboard/settings
### Rutas dinámicas
app/blog/[slug]/page.tsx -> /blog/my-post (un solo parámetro) app/shop/[...slug]/page.tsx -> /shop/a/b/c (catch-all, obligatorio) app/docs/[[...slug]]/page.tsx -> /docs o /docs/a/b (catch-all opcional)
### Grupos de rutas
app/(marketing)/about/page.tsx -> /about (sin /marketing en la URL) app/(shop)/cart/page.tsx -> /cart app/(auth)/login/layout.tsx -> layout separado para páginas de auth
Los grupos de rutas con paréntesis NO afectan la URL. Úsalos para:
- Organizar rutas por ámbito sin cambiar la estructura de la URL
- Aplicar distintos layouts a diferentes grupos de rutas
- Dividir el layout raíz en múltiples layouts raíz
### Rutas paralelas
app/@modal/default.tsx app/@modal/(.)photo/[id]/page.tsx app/layout.tsx -> recibe props { children, modal } app/photo/[id]/page.tsx -> destino de navegación directa
Reglas para rutas paralelas:
- Se definen con la convención @folder
- Cada slot se pasa como prop al layout padre
- Los slots se renderizan de forma independiente y pueden tener su propio loading/error
- SIEMPRE crea un default.tsx por cada slot (evita 404 al actualizar la página)
- Los slots NO afectan la estructura de la URL
### Rutas interceptadas
(.)folder -> intercepta en el mismo nivel (..)folder -> intercepta un nivel arriba (..)(..)folder -> intercepta dos niveles arriba (...)folder -> intercepta desde la raíz
Patrón habitual: interception de modal:
app/@modal/(.)photo/[id]/page.tsx -> la navegación suave muestra el modal app/photo/[id]/page.tsx -> la navegación directa muestra la página completa
## Árbol de decisiones: elegir un patrón de routing
1. **¿Necesitas un contenedor compartido que persista entre navegaciones?**
- Sí -> Usa layout.tsx
- Sí, pero debe volver a montarse -> Usa template.tsx
2. **¿Necesitas varias secciones independientes en la misma página?**
- Sí -> Usa rutas paralelas (@slot)
- Cada sección carga de forma independiente -> Añade loading.tsx por slot
3. **¿Necesitas un modal que se abra en navegación suave pero muestre una página completa en navegación directa?**
- Sí -> Usa rutas interceptadas + rutas paralelas
- Crea el slot @modal con ruta interceptada (.)
4. **¿Necesitas layouts distintos para distintas secciones?**
- Sí -> Usa grupos de rutas: (marketing), (dashboard), (auth)
5. **¿Necesitas segmentos dinámicos en la URL?**
- Un solo parámetro -> [param]
- Varios segmentos -> [...params] o [[...params]]
6. **¿Necesitas proteger rutas?**
- Redirecciones simples -> middleware.ts
- Lógica de auth compleja -> layout.tsx con comprobación de auth
- Protección de API -> route.ts con validación de auth
## Middleware
```typescript
// middleware.ts (raíz del proyecto o src/)
import \{ NextResponse \} from "next/server";
import type \{ NextRequest \} from "next/server";
export function middleware(request: NextRequest) \{
// Comprobar autenticación
const token = request.cookies.get("session");
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) \{
return NextResponse.redirect(new URL("/login", request.url));
\}
return NextResponse.next();
\}
export const config = \{
// Coincide con todas las rutas excepto archivos estáticos y API
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
\};
Reglas del middleware:
- Se ejecuta en CADA petición coincidente (mantenlo rápido, por debajo de 200 ms)
- No puede acceder directamente a la base de datos (limitaciones de Edge Runtime)
- Úsalo para: redirecciones, rewrites, inyección de cabeceras, comprobaciones de tokens de auth
- NO lo uses para: obtención de datos compleja, computación pesada
Antipatrones comunes de routing
- Poner lógica de API en page.tsx - Usa route.ts para endpoints de API
- Layouts anidados en profundidad de los que no puedes salir - Usa grupos de rutas para escapar
- Falta default.tsx en rutas paralelas - Provoca 404 al actualizar la página
- Usar redirect() en el render - Úsalo en Server Components o middleware, no en Client Components
- Anidar rutas en exceso - Mantén las rutas a 3-4 niveles como máximo; usa grupos de rutas para organizar
- Routing en el cliente para auth - Prefiere middleware para redirecciones de auth (se ejecuta antes del render)
Plantillas de estructura de archivos
App de e-commerce
app/
(shop)/
layout.tsx # Layout de tienda con nav
page.tsx # Inicio
products/
page.tsx # Listado de productos
[slug]/page.tsx # Detalle de producto
cart/page.tsx
checkout/page.tsx
(account)/
layout.tsx # Layout de cuenta
profile/page.tsx
orders/page.tsx
@modal/
default.tsx
(.)products/[slug]/page.tsx # Modal de vista rápida de producto
App de dashboard
app/
(auth)/
login/page.tsx
register/page.tsx
(dashboard)/
layout.tsx
page.tsx
@sidebar/
default.tsx
page.tsx
@main/
default.tsx
[section]/page.tsx
settings/
layout.tsx
page.tsx
profile/page.tsx
billing/page.tsx
## Ejemplo en funcionamiento
### Ejemplo 1: El usuario pregunta "¿Cómo creo un patrón de modal para fotos?"
**Prompt del usuario:** "Quiero que al hacer clic en una miniatura de foto se abra un modal, pero al compartir la URL se muestre la página completa de la foto."
**La respuesta guiada por el skill produciría esta estructura de archivos:**
app/ layout.tsx @modal/ default.tsx (.)photo/[id]/page.tsx # Versión modal photo/[id]/page.tsx # Versión de página completa gallery/page.tsx # Cuadrícula de miniaturas
```tsx
// app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
{modal}
</body>
</html>
);
}
// app/@modal/default.tsx
export default function Default() {
return null; // Sin modal por defecto
}// app/@modal/(.)photo/[id]/page.tsx
import { Modal } from "@/components/Modal";
export default async function PhotoModal({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<Modal>
<img src={photo.url} alt={photo.alt} />
<p>{photo.description}</p>
</Modal>
);
}// app/photo/[id]/page.tsx
export default async function PhotoPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<main className="flex min-h-screen items-center justify-center">
<img src={photo.url} alt={photo.alt} className="max-w-4xl" />
<h1>{photo.title}</h1>
<p>{photo.description}</p>
</main>
);
}Ejemplo 2: El usuario pregunta "¿Cómo debería organizar las rutas de una app SaaS?"
La respuesta guiada por el skill recomendaría:
- Grupos de rutas:
(marketing)para páginas públicas,(app)para el dashboard autenticado,(auth)para login/registro - Rutas paralelas para sidebar del dashboard + contenido principal
- Middleware para redirecciones de auth
- Estructura de archivos específica con layouts por grupo
Inmersión profunda
Cómo funciona el skill
Este skill proporciona a Claude:
- Referencia completa de convenciones de archivos - Cada archivo especial del App Router y qué hace
- Árboles de decisiones - Lógica estructurada para elegir el patrón adecuado según el requisito
- Detección de antipatrones - Errores habituales sobre los que el skill advierte de forma proactiva
- Plantillas de estructura de archivos - Arquitecturas listas para tipos de app habituales
Personalización
Amplía este skill añadiendo:
- Las convenciones de estructura de rutas específicas de tu proyecto
- Patrones de middleware personalizados (p. ej., pruebas A/B, feature flags)
- Convenciones de nombres específicas de la organización para grupos de rutas
Cómo instalar
mkdir -p .claude/skills/nextjs-routing
# Pega el contenido de Receta en .claude/skills/nextjs-routing/SKILL.mdTrampas
- Los slots de rutas paralelas son posicionales - Si renombras una carpeta de slot, debes actualizar las props del layout padre para que coincidan.
- Las rutas interceptadas usan rutas relativas al sistema de archivos -
(.)significa el mismo nivel en el sistema de archivos, no en la URL. Esto confunde a muchos desarrolladores. - default.tsx es obligatorio en rutas paralelas - Sin él, al actualizar la página en una URL que no coincide con el slot se renderizará un 404 en lugar del fallback.
- Los grupos de rutas pueden crear múltiples layouts raíz - Si dos grupos de rutas en la raíz de app tienen cada uno un layout.tsx, la navegación entre ellos provoca una recarga completa de la página.
- params es una Promise en Next.js 15+ - Debes hacer
await paramsen componentes page y layout.
Alternativas
| Enfoque | Cuándo usarlo |
|---|---|
| Pages Router | Proyectos legacy de Next.js aún no migrados |
| React Router v7 | Apps React sin Next.js, migración desde Remix |
| TanStack Router | Routing con tipos seguros y validación de search params |
| Expo Router | Routing basado en archivos para React Native + web |
FAQs
¿Cuál es la diferencia entre layout.tsx y template.tsx?
layout.tsxenvuelve children y preserva el state entre navegaciones (no se vuelve a montar)template.tsxenvuelve children pero se vuelve a montar en cada navegación (state nuevo cada vez)- Usa
template.tsxcuando necesites animaciones o reinicios de state al cambiar de ruta
¿Cómo funcionan las rutas paralelas y qué es la convención @folder?
- Las rutas paralelas se definen con
@folder(p. ej.,@modal,@sidebar) - Cada slot se pasa como prop al
layout.tsxpadre - Los slots se renderizan de forma independiente y pueden tener su propio
loading.tsxyerror.tsx - Los slots NO afectan la estructura de la URL
Trampa: ¿Qué ocurre si olvidas default.tsx en un slot de ruta paralela?
- Al actualizar la página en una URL que no coincide con el slot se renderizará un 404 en lugar de un fallback
- Crea siempre un
default.tsxpara cada slot de ruta paralela - El
default.tsxsuele devolvernullcuando no debe mostrarse contenido
¿Cómo funciona el patrón modal con rutas interceptadas?
- Crea un slot de ruta paralela (p. ej.,
@modal) con una ruta interceptada como(.)photo/[id]/page.tsx - La navegación suave (clic en Link) muestra la versión modal
- La navegación directa (visita directa a la URL o actualización) muestra la página completa en
photo/[id]/page.tsx - El prefijo
(.)significa "interceptar en el mismo nivel del sistema de archivos"
¿Qué significan los prefijos de rutas interceptadas (.), (..), (..)(..) y (...)?
(.)folder-- intercepta en el mismo nivel(..)folder-- intercepta un nivel arriba(..)(..)folder-- intercepta dos niveles arriba(...)folder-- intercepta desde la raíz de la app
¿Cómo afectan los grupos de rutas con paréntesis a la URL?
- Los grupos de rutas como
(marketing)o(auth)NO aparecen en la URL app/(marketing)/about/page.tsxse mapea a/about, no a/marketing/about- Úsalos para organizar rutas, aplicar distintos layouts o dividir layouts raíz
¿Cuáles son las reglas y limitaciones del middleware descritas en esta página?
- El middleware se ejecuta en cada petición coincidente -- mantenlo rápido (por debajo de 200 ms)
- No puede acceder directamente a la base de datos (limitaciones de Edge Runtime)
- Úsalo para: redirecciones, rewrites, inyección de cabeceras, comprobaciones de tokens de auth
- NO lo uses para: obtención de datos compleja o computación pesada
Trampa: ¿Cuál es la diferencia entre rutas catch-all [...slug] y [[...slug]]?
[...slug]es un catch-all obligatorio -- coincide con/shop/a/b/cpero NO con/shop[[...slug]]es un catch-all opcional -- coincide tanto con/docscomo con/docs/a/b- Usa catch-all opcional cuando la ruta base sin segmentos también deba ser válida
¿Cómo tipar la prop params en un componente page de Next.js 15+?
export default async function Page({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
// ...
}- En Next.js 15+,
paramses unaPromisey debe hacerse await
¿Cuáles son los antipatrones de routing habituales listados en esta página?
- Poner lógica de API en
page.tsxen lugar deroute.ts - Layouts anidados en profundidad de los que no puedes salir (usa grupos de rutas para escapar)
- Falta de
default.tsxen rutas paralelas - Usar
redirect()en Client Components en lugar de middleware - Anidar rutas más allá de 3-4 niveles de profundidad
- Routing en el cliente para auth en lugar de middleware
¿Cómo estructurar las rutas de una app SaaS con secciones públicas y autenticadas?
- Usa grupos de rutas:
(marketing)para páginas públicas,(app)para el dashboard,(auth)para login/registro - Cada grupo puede tener su propio
layout.tsx - Usa middleware para redirecciones de auth entre grupos
¿Qué tipo TypeScript debe usar el componente layout para las props de slots de rutas paralelas?
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}{modal}</body>
</html>
);
}- Cada nombre de carpeta
@slotse convierte en una prop tipada comoReact.ReactNode