Carga y Manejo de Errores
Los archivos especiales loading.tsx, error.tsx y not-found.tsx crean límites Suspense y Error Boundaries automáticos con alcance a segmentos de ruta.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
app/
├── loading.tsx # Fallback de carga global
├── error.tsx # Error boundary global
├── not-found.tsx # 404 global
└── dashboard/
├── loading.tsx # Fallback de carga para /dashboard
├── error.tsx # Error boundary para /dashboard
└── page.tsx
// app/dashboard/loading.tsx
export default function Loading() {
return <div className="animate-pulse">Cargando dashboard...</div>;
}
// app/dashboard/error.tsx
"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>
);
}Cuándo usarlo: Cada segmento de ruta que obtiene datos debe tener un loading.tsx. Cada segmento donde los errores son recuperables debe tener un error.tsx.
Ejemplo Funcional
// app/dashboard/loading.tsx — Estado de carga esqueleto
export default function DashboardLoading() {
return (
<div className="space-y-4 p-6">
<div className="h-8 w-48 animate-pulse rounded bg-gray-200" />
<div className="grid grid-cols-3 gap-4">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-32 animate-pulse rounded-lg bg-gray-200" />
))}
</div>
<div className="h-64 animate-pulse rounded-lg bg-gray-200" />
</div>
);
}// app/dashboard/error.tsx — Debe ser un Client Component
"use client";
import { useEffect } from "react";
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Registrar error en el servicio de monitoreo
console.error("Error del dashboard:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center p-12">
<h2 className="text-xl font-bold text-red-600">Error del Dashboard</h2>
<p className="mt-2 text-gray-600">
{error.message || "Ocurrió un error inesperado."}
</p>
{error.digest && (
<p className="mt-1 text-sm text-gray-400">ID de Error: {error.digest}</p>
)}
<button
onClick={reset}
className="mt-4 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Intentar de Nuevo
</button>
</div>
);
}// app/dashboard/page.tsx — Server Component que podría fallar
import { notFound } from "next/navigation";
async function getDashboardData() {
const res = await fetch("https://api.example.com/dashboard", {
next: { revalidate: 60 },
});
if (res.status === 404) notFound();
if (!res.ok) throw new Error("No se pudo cargar los datos del dashboard");
return res.json();
}
export default async function DashboardPage() {
const data = await getDashboardData();
return (
<div className="p-6">
<h1 className="text-2xl font-bold">Dashboard</h1>
<div className="mt-4 grid grid-cols-3 gap-4">
<StatCard label="Ingresos" value={data.revenue} />
<StatCard label="Usuarios" value={data.users} />
<StatCard label="Pedidos" value={data.orders} />
</div>
</div>
);
}
function StatCard({ label, value }: { label: string; value: number }) {
return (
<div className="rounded-lg border p-4">
<p className="text-sm text-gray-500">{label}</p>
<p className="text-2xl font-bold">{value.toLocaleString()}</p>
</div>
);
}// app/not-found.tsx — Página personalizada 404
import Link from "next/link";
export default function NotFound() {
return (
<div className="flex min-h-screen flex-col items-center justify-center">
<h1 className="text-6xl font-bold">404</h1>
<p className="mt-4 text-xl text-gray-600">Página no encontrada</p>
<Link
href="/"
className="mt-6 rounded bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
>
Ir a Inicio
</Link>
</div>
);
}Profundización
Cómo Funciona
loading.tsxenvuelve la página en un límite<Suspense>. Next.js genera automáticamente<Suspense fallback={<Loading />}><Page /></Suspense>. La interfaz de carga se muestra instantáneamente mientras la página se transmite.error.tsxenvuelve la página en un Error Boundary de React. Captura errores de JavaScript en la página y sus hijos durante el renderizado. La funciónresetvuelve a renderizar el contenido del error boundary.error.tsxdebe ser un Client Component. Los Error Boundaries son una característica de React del lado del cliente. Siempre agrega"use client"al inicio.error.tsxno captura errores en el layout del mismo nivel. El error boundary se sitúa entre el layout y la página. Para capturar errores de layout, colocaerror.tsxen el segmento padre.not-found.tsxse activa en llamadasnotFound()o rutas no coincidentes. Elnot-found.tsxraíz es el fallback para todas las URLs no coincidentes. Elnot-found.tsxanidado solo se activa cuando llamas explícitamente anotFound().- Los estados de carga y error son por segmento. Cada carpeta puede tener sus propios archivos de carga y error, dando control granular sobre qué partes de la interfaz muestran placeholders o estados de error.
- El streaming funciona con
loading.tsx. El layout se renderiza inmediatamente, el fallback de carga se muestra, y el contenido de la página se transmite cuando está listo.
Variaciones
// Carga anidada — solo el segmento más interno muestra carga
// app/dashboard/settings/loading.tsx
export default function SettingsLoading() {
return <p>Cargando configuración...</p>;
// El layout del dashboard y la barra lateral permanecen visibles
}// Error boundary global — captura errores en toda la aplicación
// app/global-error.tsx
"use client";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h1>Algo salió muy mal</h1>
<button onClick={reset}>Intentar de nuevo</button>
</body>
</html>
);
}
// Nota: global-error.tsx reemplaza el layout raíz, así que debe incluir <html> y <body>// Límites Suspense manuales para estados de carga granulares
// app/dashboard/page.tsx
import { Suspense } from "react";
export default function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<Skeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<Skeleton />}>
<UserTable />
</Suspense>
</div>
);
}
async function RevenueChart() {
const data = await getRevenue(); // se transmite independientemente
return <div>{/* chart */}</div>;
}
async function UserTable() {
const users = await getUsers(); // se transmite independientemente
return <table>{/* rows */}</table>;
}
function Skeleton() {
return <div className="h-48 animate-pulse rounded bg-gray-200" />;
}Notas de TypeScript
// Tipo de props de error.tsx
interface ErrorProps {
error: Error & { digest?: string }; // digest es un hash de error del lado del servidor
reset: () => void; // vuelve a renderizar el error boundary
}
// loading.tsx y not-found.tsx no aceptan props
// Son componentes simples sin parámetros
// global-error.tsx tiene los mismos props que error.tsx
// pero debe renderizar su propio <html> y <body>Trampas
error.tsxdebe tener"use client". Olvidarlo causa un error de compilación. Los Error Boundaries son inherentemente del lado del cliente.error.tsxno puede capturar errores en sulayout.tsxhermano. El boundary envuelve la página, no el layout. Usa unerror.tsxpadre para capturar errores de layout.global-error.tsxsolo se activa en producción. En desarrollo, aparece la superposición de errores de Next.js.global-error.tsxdebe incluir<html>y<body>. Reemplaza completamente el layout raíz cuando se activa.notFound()denext/navigationlanza un error. El código después denotFound()es inalcanzable. TypeScript podría no advertir sobre esto.loading.tsxse muestra en la carga inicial y navegaciones posteriores. Aparece cada vez que el componente de página del segmento está pendiente.reset()solo funciona para errores del lado del cliente. Si un Server Component lanza un error,reset()intenta volver a renderizar pero el mismo error del servidor puede repetirse sin una corrección de código o cambio de datos.
Alternativas
| Enfoque | Cuándo Usarlo |
|---|---|
Límite <Suspense> manual | Estados de carga granulares dentro de una sola página |
| Librería de React Error Boundary | Manejo personalizado de errores en Client Components |
try/catch en Server Components | Manejo de errores sin mostrar el error boundary |
| Redirección en error | Enviar usuarios a una página diferente en lugar de mostrar una interfaz de error |
unstable_rethrow | Relanzar errores internos de Next.js (redirecciones, notFound) desde bloques catch |
Preguntas Frecuentes
¿Por qué error.tsx debe ser un Client Component?
Los Error Boundaries son una característica de React del lado del cliente. Se basan en métodos de ciclo de vida componentDidCatch que solo existen en el cliente. Debes agregar "use client" al inicio de cada archivo error.tsx.
Trampa: ¿Puede error.tsx capturar errores lanzados en su layout.tsx hermano?
No. El error boundary envuelve la página, no el layout del mismo nivel. Para capturar errores de layout, coloca error.tsx en el segmento padre.
¿Cuál es la diferencia entre error.tsx y global-error.tsx?
error.tsxcaptura errores dentro de un segmento de ruta específicoglobal-error.tsxcaptura errores en toda la aplicación, incluidos errores del layout raízglobal-error.tsxdebe incluir su propio<html>y<body>porque reemplaza el layout raízglobal-error.tsxsolo se activa en producción; en desarrollo, aparece la superposición de errores de Next.js
¿Cómo crea loading.tsx un límite Suspense automáticamente?
Next.js genera <Suspense fallback={<Loading />}><Page /></Suspense> internamente. La interfaz de carga se renderiza instantáneamente mientras el contenido de la página se transmite.
¿Se muestra loading.tsx en cada navegación o solo en la primera carga?
Se muestra en la carga inicial y en cada navegación posterior a ese segmento. Cada vez que el componente de página está pendiente, el fallback de carga aparece.
¿Cuándo se activa not-found.tsx automáticamente vs. cuándo debes llamar a notFound()?
- El
not-found.tsxraíz se activa automáticamente para todas las URLs no coincidentes - El
not-found.tsxanidado solo se activa cuando llamas explícitamente anotFound()denext/navigation
¿Qué hace realmente la función reset en error.tsx?
Vuelve a renderizar el contenido del error boundary, intentando renderizar la página de nuevo. Para errores del lado del cliente, esto vuelve a intentar el renderizado. Para errores del servidor, el mismo error puede repetirse sin un cambio de datos o código.
¿Cómo usas límites Suspense manuales para estados de carga más granulares?
import { Suspense } from "react";
export default function Page() {
return (
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<Skeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<Skeleton />}>
<UserTable />
</Suspense>
</div>
);
}Cada componente asincrónico se transmite independientemente.
¿Cuál es el tipo de TypeScript para los props de error.tsx?
interface ErrorProps {
error: Error & { digest?: string };
reset: () => void;
}La propiedad digest es un hash de error del lado del servidor útil para el registro.
Trampa: ¿Qué sucede si llamas a notFound() dentro de un bloque try/catch?
notFound() lanza un error internamente, así que el bloque catch lo interceptará. Usa unstable_rethrow en bloques catch para relanzar errores internos de Next.js como notFound() y redirect().
¿Aceptan props loading.tsx y not-found.tsx?
No. Ambos son componentes simples sin parámetros. No reciben props de Next.js.
¿Cómo ayudan los estados de carga y error por segmento con el streaming?
- El layout se renderiza inmediatamente
- El
loading.tsxde cada segmento muestra un fallback para su parte de la página - Cuando la datos de cada segmento se resuelven, se transmite y reemplaza su fallback de carga
- Un error en un segmento no bloquea a otros segmentos de renderizarse
Relacionado
- Conceptos Básicos del App Router — descripción general de convenciones de archivos
- Layouts — cómo los error boundaries interactúan con layouts
- Navegación — funciones
notFound()yredirect() - Rutas Paralelas — estados de error independientes por slot