Next.js App Router
Aplica a Next.js 14+ con directorio app/ y TypeScript.
Convenciones de archivos
app/layout.tsx— layout raíz obligatorio. Debe incluir<html>y<body>.app/page.tsx— entrada de ruta. Por defecto es un Componente Servidor de React (RSC).app/loading.tsx— fallback de streaming para el segmento.app/error.tsx— debe ser un Componente Cliente ('use client'), recibe{ error, reset }.app/not-found.tsx— UI para 404.app/route.ts— Manejador de Rutas (API). ExportaGET,POST, etc.- Dinámico:
app/blog/[slug]/page.tsxrecibe{ params: { slug: string } }. - Grupos:
app/(marketing)/about/page.tsx— la URL no se ve afectada. - Paralelo:
app/@modal/page.tsx+layout({ modal }). Interceptación:app/(.)photo/[id]/page.tsx.
Reglas
- Componentes Servidor por defecto. Agrega
'use client'solo cuando necesites hooks, eventos o APIs del navegador. Mantén los límites de cliente pequeños — colócalos en las hojas. paramsysearchParamsson asíncronos en Next 15:await params.- Nunca importes un Componente Servidor directamente en un Componente Cliente si usa funcionalidades exclusivas del servidor — pásalo como
children/propsen su lugar. - Usa
next/linkpara la navegación,next/navigation(useRouter,usePathname) solo en Componentes Cliente. Usaredirect()/notFound()denext/navigationen Componentes Servidor. - Coloca en el mismo segmento:
page.tsx,loading.tsx,error.tsx. La UI compartida va encomponents/, no enapp/a menos que esté enrutada.
Patrones
// app/blog/[slug]/page.tsx — params asíncronos en Next 15
type Props = { params: Promise<{ slug: string }> };
export default async function BlogPost({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return <article>{post.title}</article>;
}
export async function generateStaticParams() {
const slugs = await getAllSlugs();
return slugs.map((slug) => ({ slug }));
}
// app/error.tsx — debe ser un Componente Cliente
'use client';
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Algo salió mal</h2>
<button onClick={() => reset()}>Intentar de nuevo</button>
</div>
);
}
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Lista de verificación
- ¿El nombre del archivo y la ubicación del segmento son correctos?
- ¿La directiva de cliente solo donde sea necesaria?
- ¿
loading.tsx/error.tsxpara segmentos asíncronos? - ¿
generateStaticParams+generateMetadatadonde el SEO importe? - ¿No se usa
useRouterdenext/router(importación del pages-router)?