Conceptos básicos de App Router
El App Router utiliza un enrutador basado en el sistema de archivos donde las carpetas definen rutas y los archivos especiales definen la interfaz de usuario y el comportamiento.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
app/
├── layout.tsx # Layout raíz (requerido)
├── page.tsx # Ruta de inicio → /
├── loading.tsx # Interfaz de carga para /
├── error.tsx # Límite de error para /
├── not-found.tsx # Interfaz 404 para /
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── layout.tsx # Layout anidado para /blog/*
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── api/
└── health/
└── route.tsx # GET /api/health
Regla clave: Una ruta es solo públicamente accesible cuando una carpeta contiene un archivo page.tsx o route.tsx.
Ejemplo de trabajo
// app/layout.tsx — Layout raíz (requerido, envuelve cada página)
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "My App",
description: "Built with Next.js App Router",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}// app/page.tsx — Página de inicio (Server Component por defecto)
export default function HomePage() {
return (
<main>
<h1>Welcome</h1>
<p>This is a Server Component — no client JS shipped.</p>
</main>
);
}// app/dashboard/page.tsx — Ruta anidada en /dashboard
export default function DashboardPage() {
return <h1>Dashboard</h1>;
}// app/api/health/route.tsx — Route Handler (endpoint de API)
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ status: "ok", timestamp: Date.now() });
}Cuándo usarlo: Cada proyecto de Next.js 15+ usa el App Router. Comienza aquí cuando crees cualquier ruta nueva, layout o endpoint de API.
Análisis profundo
Cómo funciona
- Las carpetas definen segmentos de ruta. Cada carpeta bajo
app/se asigna a un segmento de URL.app/blog/settings/page.tsxsirve/blog/settings. - Los archivos especiales tienen significado reservado. Next.js reconoce
page.tsx,layout.tsx,loading.tsx,error.tsx,not-found.tsx,route.tsx,template.tsxydefault.tsx. - Los Server Components son el predeterminado. Los archivos en el App Router son React Server Components a menos que agregues
"use client"en la parte superior. - La colocación es segura. Los archivos no especiales (componentes, utils, estilos) colocados dentro de carpetas de ruta no se exponen como rutas.
route.tsxypage.tsxno pueden coexistir en la misma carpeta. Un segmento de ruta es una página o una ruta de API, no ambas.- Los layouts persisten en las navegaciones. No se remontan cuando una ruta secundaria cambia, preservando el estado.
loading.tsxcrea un límite<Suspense>automático. Next.js envuelve la página en Suspense usandoloading.tsxcomo fallback.error.tsxcrea un Error Boundary automático. Captura errores en la página y sus hijos pero no en el layout del mismo nivel.
Variaciones
// app/template.tsx — Como layout, pero se remonta en cada navegación
export default function Template({ children }: { children: React.ReactNode }) {
return <div className="animate-fade-in">{children}</div>;
}// app/not-found.tsx — Página global 404
export default function NotFound() {
return (
<div>
<h2>404 — Page Not Found</h2>
<p>The page you are looking for does not exist.</p>
</div>
);
}// app/api/users/route.tsx — Manejador de ruta con múltiples métodos
import { NextRequest, NextResponse } from "next/server";
export async function GET() {
const users = await db.user.findMany();
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}Notas de TypeScript
// Next.js proporciona tipos integrados para props de página y layout
// Los props de página en Next.js 15+ usan params basados en Promise
interface PageProps {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
// Los props de layout siempre incluyen children
interface LayoutProps {
children: React.ReactNode;
params: Promise<{ slug: string }>;
}Problemas comunes
layout.tsxno se re-renderiza en la navegación. Si necesitas estado fresco en cada navegación, usatemplate.tsxen su lugar.error.tsxno captura errores en el layout del mismo nivel. Para capturar errores de layout, colocaerror.tsxen el segmento padre.route.tsxdebe exportar métodos HTTP nombrados (GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS). Las exportaciones predeterminadas se ignoran.- Los parámetros son ahora asincrónico en Next.js 15+. Debes
await paramsdentro de páginas y layouts — la API sincrónica está deprecada. searchParamstambién es asincrónico en Next.js 15+. Usaawait searchParamsouse(searchParams)— no desestructures sincrónicamente.not-found.tsxen la raíz captura todas las rutas sin coincidencias. Elnot-found.tsxa nivel de segmento solo se activa cuando llamas anotFound().
Alternativas
| Enfoque | Cuándo usar |
|---|---|
Pages Router (directorio pages/) | Proyectos legados no migrados aún |
Manejador route.tsx | Endpoints solo de API sin interfaz de usuario |
template.tsx | Necesitas una instancia de componente fresca en cada navegación |
| Enrutador de terceros (TanStack Router) | Aplicaciones React no de Next.js que necesitan enrutamiento type-safe |
Preguntas frecuentes
¿Qué hace que una ruta sea públicamente accesible en el App Router?
Una ruta es solo públicamente accesible cuando una carpeta contiene un archivo page.tsx o route.tsx. Otros archivos (componentes, utils, estilos) colocados en la carpeta no se exponen como rutas.
¿Pueden coexistir page.tsx y route.tsx en la misma carpeta?
No. Un segmento de ruta es una página o una ruta de API, nunca ambas. Coloca tu route.tsx en una carpeta separada (p. ej., app/api/health/route.tsx).
¿Son los componentes en el App Router Server Components o Client Components por defecto?
Server Components por defecto. Debes agregar "use client" en la parte superior de un archivo para convertirlo en un Client Component.
¿Cuál es la diferencia entre layout.tsx y template.tsx?
layout.tsxpersiste en las navegaciones y no se remontatemplate.tsxse remonta en cada navegación, dando estado fresco cada vez- Usa
template.tsxpara animaciones de entrada/salida o logging por navegación
¿Por qué error.tsx no captura errores lanzados en el layout.tsx del mismo nivel?
El límite de error creado por error.tsx envuelve la página, no el layout hermano. Para capturar errores de layout, coloca error.tsx en el segmento padre.
¿Cómo funciona loading.tsx bajo el capó?
Next.js envuelve automáticamente la página en <Suspense fallback={<Loading />}>. La interfaz de carga se muestra instantáneamente mientras la página se carga.
Gotcha: ¿Qué sucede si desestructuras params sincrónicamente en Next.js 15+?
Se rompe. Tanto params como searchParams ahora son objetos Promise en Next.js 15+. Debes await params dentro de páginas y layouts. La API sincrónica está deprecada.
¿Cómo defines un manejador de ruta de API con múltiples métodos HTTP?
// app/api/users/route.tsx
import { NextRequest, NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ users: [] });
}
export async function POST(request: NextRequest) {
const body = await request.json();
return NextResponse.json(body, { status: 201 });
}¿Dónde se activa not-found.tsx automáticamente versus manualmente?
- El
not-found.tsxraíz captura todas las rutas sin coincidencias automáticamente - El
not-found.tsxa nivel de segmento solo se activa cuando llamas anotFound()desdenext/navigation
¿Cuáles son los tipos de TypeScript para props de página y layout en Next.js 15+?
interface PageProps {
params: Promise<{ slug: string }>;
searchParams: Promise<{
[key: string]: string | string[] | undefined;
}>;
}
interface LayoutProps {
children: React.ReactNode;
params: Promise<{ slug: string }>;
}¿Cómo tipas un manejador de ruta que acepta tanto GET como POST?
import { NextRequest, NextResponse } from "next/server";
export async function GET(): Promise<NextResponse> {
return NextResponse.json({ status: "ok" });
}
export async function POST(
request: NextRequest
): Promise<NextResponse> {
const body = await request.json();
return NextResponse.json(body, { status: 201 });
}Gotcha: ¿Qué sucede si usas una exportación predeterminada en route.tsx?
Se ignora. route.tsx debe exportar métodos HTTP nombrados (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Las exportaciones predeterminadas no hacen nada.
¿Es seguro colocar archivos que no son de ruta dentro de carpetas de ruta?
Sí. Los archivos no especiales (componentes, utils, estilos, pruebas) colocados dentro de carpetas de ruta no se exponen como rutas. Solo los archivos con nombres reservados como page.tsx y route.tsx se tratan como rutas.
Relacionado
- Layouts — composición de layout anidado
- Dynamic Routes — segmentos de URL parametrizados
- Loading & Error — patrones de loading.tsx y error.tsx
- Route Groups — organizar sin afectar la estructura de URL