//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Maneja errores de forma elegante en Next.js 15+ App Router usando error.tsx límites, global-error.tsx para fallos a nivel raíz, not-found.tsx para 404s, y almacenamiento de errores estructurado.
// app/dashboard/error.tsx
"use client";
import { useEffect } from "react";
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Registra en tu servicio de reporte de errores
console.error("Error del dashboard:", error);
}, [error]);
return (
<div role="alert">
<h2>Algo salió mal</h2>
<p>{error.message}</p>
{error.digest && (
<p className="text-sm text-gray-500">ID del Error: {error.digest}</p>
)}
<button onClick={reset}>Intentar de nuevo</button>
</div>
);
}// app/global-error.tsx
"use client";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<div role="alert">
<h1>Error de la Aplicación</h1>
<p>Ocurrió un error inesperado.</p>
<button onClick={reset}>Recargar</button>
</div>
</body>
</html>
);
}// app/not-found.tsx
import Link from "next/link";
export default function NotFound() {
return (
<div>
<h1>404 - Página No Encontrada</h1>
<p>La página que buscas no existe.</p>
<Link href="/">Ir al inicio</Link>
</div>
);
}// app/posts/[slug]/page.tsx
import { notFound } from "next/navigation";
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await db.post.findUnique({ where: { slug } });
if (!post) {
notFound(); // Renderiza el not-found.tsx más cercano
}
return <article>{post.content}</article>;
}// lib/logger.ts
type ErrorContext = {
userId?: string;
path?: string;
action?: string;
metadata?: Record<string, unknown>;
};
export function logError(error: unknown, context?: ErrorContext) {
const errorObj = error instanceof Error ? error : new Error(String(error));
const payload = {
message: errorObj.message,
stack: errorObj.stack,
timestamp: new Date().toISOString(),
...context,
};
// Reemplaza con Sentry, Axiom, o tu servicio preferido
if (process.env.NODE_ENV === "production") {
fetch("/api/log", {
method: "POST",
body: JSON.stringify(payload),
}).catch(() => {
// Suprime errores de almacenamiento para evitar fallos en cascada
});
} else {
console.error("[Error]", payload);
}
}// app/actions.ts
"use server";
import { logError } from "@/lib/logger";
type ActionResult<T> =
| { success: true; data: T }
| { success: false; error: string };
export async function createPost(
formData: FormData
): Promise<ActionResult<{ id: string }>> {
try {
const title = formData.get("title") as string;
if (!title) {
return { success: false, error: "El título es obligatorio" };
}
const post = await db.post.create({ data: { title } });
return { success: true, data: { id: post.id } };
} catch (error) {
logError(error, { action: "createPost" });
return { success: false, error: "No se pudo crear la publicación" };
}
}error.tsx es un Client Component que envuelve los hijos del segmento de ruta en un React Error Boundary. Detecta errores lanzados durante el renderizado, en Server Components, y durante la obtención de datos.global-error.tsx detecta errores en el layout raíz. Debe renderizar sus propias etiquetas <html> y <body> porque reemplaza el layout raíz completo cuando se activa.not-found.tsx se renderiza cuando se llama a notFound() o cuando ninguna ruta coincide. Se usa el not-found.tsx más cercano en el árbol de componentes.useEffect, o código asincrónico en Client Components. Usa try/catch para esos casos.digest es un hash generado por Next.js para errores del lado del servidor. Permite correlacionar errores visibles para el usuario con registros del servidor sin exponer trazas sensibles.reset intenta volver a renderizar los hijos del error boundary. Funciona para errores transitorios (problemas de red) pero no para bugs persistentes.Error Boundary con Reintentos y Fallback:
"use client";
import { useEffect, useState } from "react";
export default function ErrorWithRetry({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
const [retryCount, setRetryCount] = useState(0);
useEffect(() => {
if (retryCount > 0) {
reset();
}
}, [retryCount, reset]);
if (retryCount >= 3) {
return (
<div>
<h2>Error Persistente</h2>
<p>Por favor contacta soporte. ID del Error: {error.digest}</p>
</div>
);
}
return (
<div role="alert">
<h2>Ocurrió un Error</h2>
<button onClick={() => setRetryCount((c) => c + 1)}>
Reintentar ({3 - retryCount} intentos restantes)
</button>
</div>
);
}Manejo de Errores en Route Handler:
// app/api/posts/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logError } from "@/lib/logger";
export async function GET(request: NextRequest) {
try {
const posts = await db.post.findMany();
return NextResponse.json(posts);
} catch (error) {
logError(error, { path: "/api/posts", action: "GET" });
return NextResponse.json(
{ error: "Error interno del servidor" },
{ status: 500 }
);
}
}error es Error & { digest?: string }. El digest es opcional y solo está presente para errores del lado del servidor.{ success: true; data: T } | { success: false; error: string }) para manejo de errores con seguridad de tipos en el cliente.throw en Server Actions para errores de validación. Reserva throw para fallos inesperados que deberían activar el error boundary.error.tsx debe ser un Client Component. Requiere la directiva "use client". Olvidar esto produce un error de compilación.error.tsx no detecta errores en el layout.tsx del mismo nivel. Para detectar errores de layout, coloca error.tsx en el segmento padre, o usa global-error.tsx para el layout raíz.global-error.tsx solo se activa en producción. En desarrollo, se muestra la superposición de error de Next.js en su lugar.redirect() lanza un error especial. Si envuelves redirect() en un try/catch dentro de un Server Component, la redirección será capturada y suprimida. O relanza errores de NEXT_REDIRECT o llama a redirect() fuera del try/catch.digest para correlacionar con registros del servidor.| Enfoque | Ventajas | Desventajas |
|---|---|---|
error.tsx boundary | Integrado, automático, por ruta | Solo Client Component, sin errores de layout |
global-error.tsx | Detecta errores de layout raíz | Debe renderizar su propio html/body, solo producción |
| Try/catch en Server Actions | Control granular, retorna errores tipados | Manual, sin boundary automático |
| Sentry o Datadog | Seguimiento rico de errores, alertas | Dependencia externa, costo |
React ErrorBoundary clase | Control total, reutilizable | Verboso, sin errores de Server Component |
"use client" es necesaria para que error.tsx funcione como un error boundary.error.tsx solo detecta errores en los hijos del segmento de ruta.error.tsx en el segmento padre.global-error.tsx.global-error.tsx se activa, reemplaza el layout raíz completo.<html> y <body>, la página no tendría estructura de documento.redirect() lanza un error especial NEXT_REDIRECT.NEXT_REDIRECT o llama a redirect() fuera del bloque try/catch.throw para fallos inesperados que deberían activar el error boundary más cercano.{ success: false; error: string }) para errores de validación esperados.reset() vuelve a renderizar los hijos del error boundary, intentando la recuperación.type ActionResult<T> =
| { success: true; data: T }
| { success: false; error: string };
export async function createPost(
formData: FormData
): Promise<ActionResult<{ id: string }>> {
// ...
}success permite a TypeScript estrechar el tipo al verificar el resultado.export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
// ...
}Error & { digest?: string } es el tipo requerido. El digest es opcional..catch(() => {}) asegura que los fallos de almacenamiento sean silenciosos.digest, que puedes usar para encontrar el error completo en registros del servidor.useEffect.layout.tsx del mismo nivel.global-error.tsx para fallos de layout raíz.Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥