Guía completa de Next.js Error Boundary: 5 técnicas clave para gestionar errores en runtime con elegancia

El chat del equipo de operaciones explotó: «¡La home no carga! ¡Todo en blanco!»
Abrí la plataforma de monitorización: un componente de terceros falló y arrastró toda la página. Lo que veían los usuarios era una pantalla en blanco, sin siquiera un aviso de error. Páginas que iban bien en producción se caen de golpe por un formato de datos incorrecto o un timeout de API. El try-catch tradicional no llega a la capa de renderizado de React; el usuario mira la pantalla en blanco y cierra la pestaña.
Según estudios de experiencia de usuario, la pantalla en blanco provoca la pérdida de más del 80% de los usuarios.
Afortunadamente, Next.js ofrece el mecanismo Error Boundary para gestionar estos errores en runtime con elegancia. No solo evita la pantalla en blanco: muestra una interfaz de degradación amigable e incluso un botón de «Reintentar» para que el usuario se recupere solo. Este artículo repasa el uso completo de Next.js Error Boundary: desde error.tsx básico hasta global-error.tsx como red global, y el manejo especial de Server Components.
Al terminar, sabrás cómo hacer que tu app falle con más elegancia y evitar esas noches despertándote para arreglar bugs.
¿Por qué necesitas Error Boundary? Límites del manejo tradicional de errores
Cuando empecé con React, pensé que try-catch lo cubría todo. La realidad me desmintió enseguida.
Tres limitaciones graves de try-catch
La primera: solo captura errores de código síncrono. Si escribes JSON.parse(badData) dentro de un try, lo atrapa. ¿Pero un error durante el renderizado de un componente? No.
La segunda es peor: errores asíncronos en manejadores de eventos. Llamas a una API en un clic y la API falla; try-catch tampoco sirve. ¿Por qué? Porque cuando el código asíncrono se ejecuta, el contexto del try-catch ya terminó.
La tercera es la más grave: errores de renderizado en componentes React. Tu componente accede a una propiedad undefined en el return y la página queda en blanco. try-catch no ayuda aquí.
Cómo funciona React Error Boundary
React lo detectó hace tiempo e introdujo Error Boundary. La idea es simple: el árbol de componentes es como muñecas rusas; el error «burbujea» desde dentro hacia fuera hasta el Error Boundary más cercano.
Lo clásico es un componente de clase con componentDidCatch y getDerivedStateFromError. Escribir clases cada vez cansa, y con componentes funcionales esos métodos no aplican.
La solución elegante de Next.js
Con App Router en Next.js 13, Error Boundary quedó encapsulado y es muy sencillo. Creas error.tsx en un directorio de ruta y se convierte automamente en el límite de error de esa ruta. Sin clases, sin gestionar estado a mano.
Otro punto clave: el Error Boundary de Next.js cubre errores de servidor y cliente. Si un Server Component falla al renderizar en el servidor, el error.tsx más cercano lo captura. En React tradicional eso no era posible.
Solo hay que recordar: error.tsx debe ser componente cliente, con 'use client' al inicio, porque usa hooks para estado y recuperación.
Facebook Messenger es un caso clásico: envolvieron barra lateral, diálogos e input de mensajes en Error Boundaries distintos. Si una zona falla, el resto sigue. El usuario puede no notarlo.
Ese es el valor central: que un error local no se convierta en desastre global.
Uso de error.tsx: límite de error local
Vamos a lo práctico. ¿Cómo se escribe error.tsx?
Estructura básica: listo en 5 minutos
En cualquier directorio de ruta, crea error.tsx y pega esto:
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Reportar a la plataforma de monitorización, p. ej. Sentry
console.error('Error capturado:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<h2 className="text-2xl font-bold mb-4">Ups, algo salió mal</h2>
<p className="text-gray-600 mb-4">
{error.message || 'No se pudo cargar la página'}
</p>
<button
onClick={() => reset()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Reintentar
</button>
</div>
)
}
Puntos clave:
- No omitas ‘use client’: sin esa línea, Next.js fallará
- Objeto error: incluye mensaje y stack;
digestes nuevo en Next.js 15 para rastreo - Función reset: al pulsar, vuelve a renderizar el contenido dentro del límite de error
Mecanismo de burbujeo: sube como un ascensor
Al principio cuesta un poco. Con esta estructura de directorios se entiende:
app/
├── layout.tsx # Layout raíz
├── error.tsx # Captura errores bajo la ruta raíz (A)
├── page.tsx # Home
├── dashboard/
│ ├── layout.tsx # Layout de dashboard
│ ├── error.tsx # Captura errores bajo dashboard (B)
│ └── page.tsx # Página dashboard
└── profile/
└── page.tsx # Página profile
Si dashboard/page.tsx falla al renderizar, ¿quién lo captura? (B), el error.tsx padre más cercano.
¿Y si falla profile/page.tsx? No hay error.tsx en profile; el error sigue subiendo hasta (A).
Trampa habitual: error.tsx no captura errores del layout.tsx del mismo nivel. El límite va dentro del layout; si el layout cae, el boundary aún no está cargado. Para dashboard/layout.tsx, hay que manejarlo en app/error.tsx.
Uso correcto de reset()
reset() vuelve a renderizar el subárbol bajo el componente de error. Sirve para errores transitorios:
- Timeout de API (reintentar puede funcionar)
- Red inestable al cargar recursos
- Condiciones límite por input del usuario
Si es un bug de código, p. ej. undefined.property, reintentar no ayuda: hay que corregir y desplegar.
Algunos equipos limitan reintentos: tras 3 intentos ocultan «Reintentar» y piden refrescar o contactar soporte:
'use client'
import { useEffect, useState } from 'react'
export default function Error({ error, reset }: {
error: Error & { digest?: string }
reset: () => void
}) {
const [retryCount, setRetryCount] = useState(0)
const handleReset = () => {
setRetryCount(prev => prev + 1)
reset()
}
return (
<div>
<h2>Algo salió mal</h2>
{retryCount < 3 ? (
<button onClick={handleReset}>
Reintentar ({retryCount}/3)
</button>
) : (
<p>Varios reintentos fallaron. Actualiza la página o <a href="/contact">contáctanos</a></p>
)}
</div>
)
}
global-error.tsx: red de seguridad global
error.tsx es potente, pero no captura errores del layout raíz app/layout.tsx. Ahí entra global-error.tsx.
¿Cuándo usar global-error.tsx?
En producción se dispara poco. Cubre sobre todo:
- Fallo al inicializar el layout raíz (p. ej. la librería de estado global)
- Errores que ningún error.tsx capturó
Es la última red: esperas no usarla, pero debe existir.
Particularidades de global-error.tsx
A diferencia de error.tsx, debe incluir HTML completo: etiquetas <html> y <body>.
¿Por qué? Sustituye por completo el layout raíz. Si el layout cae, no queda marco de página; global-error.tsx monta una página mínima viable.
Código completo:
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
padding: '20px',
fontFamily: 'system-ui, sans-serif'
}}>
<h1>La aplicación tuvo un problema grave</h1>
<p style={{ color: '#666', marginBottom: '20px' }}>
{process.env.NODE_ENV === 'development'
? error.message
: 'Estamos trabajando en ello. Inténtalo más tarde'}
</p>
<button
onClick={() => reset()}
style={{
padding: '10px 20px',
background: '#0070f3',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
Recargar la aplicación
</button>
</div>
</body>
</html>
)
}
Uso estilos en línea, no Tailwind ni CSS modules: el sistema de estilos puede no haber cargado; hay que garantizar que se vea algo.
Desarrollo vs producción
Detalle: global-error.tsx solo actúa en producción. En desarrollo, Next.js sigue mostrando la página roja con stack para depurar.
En producción conviene ocultar detalles técnicos. El process.env.NODE_ENV del ejemplo hace eso. Al usuario no le importa «TypeError: Cannot read property ‘map’ of undefined»; quiere saber si puede usar la app y cuándo se arregla.
¿Añadir global-error.tsx?
Sí. La probabilidad es baja, pero el impacto es alto. Al menos el usuario ve una página de error decente, no el «No se puede acceder a este sitio» del navegador.
Como un seguro: no quieres usarlo, pero mejor tenerlo.
Consideraciones especiales: errores en Server Components
Los Server Components de Next.js 13+ cambian el manejo de errores. Servidor y cliente no se tratan igual.
¿A dónde van los errores de Server Components?
Al principio me confundía: si el componente renderiza en el servidor y falla, ¿lo captura error.tsx en el cliente?
Sí. Next.js pasa el error al cliente y activa el error.tsx más cercano. En producción el mensaje se sanitiza para no filtrar datos del servidor.
Si falla la conexión a la base de datos, en desarrollo ves el stack completo; en producción solo «Error al cargar».
Errores esperados vs inesperados
Next.js lo enfatiza en la documentación. Hay que distinguir:
Esperados: dentro de la lógica de negocio, manejo explícito
- Validación de formulario
- API 404 (dato inexistente)
- Permisos insuficientes (usuario no autenticado)
Inesperados: bugs o fallos de sistema → Error Boundary
- Fallo de conexión a base de datos
- Caída de servicio de terceros
- Acceso a propiedad undefined
Para esperados, usa try-catch en Server Actions o funciones de datos y devuelve el error al componente:
// app/actions.ts
'use server'
export async function createUser(formData: FormData) {
const email = formData.get('email') as string
// Error esperado: formato de email inválido
if (!email.includes('@')) {
return { error: 'Introduce un email válido' }
}
try {
await db.user.create({ email })
return { success: true }
} catch (error) {
// Error inesperado: base de datos caída; dejar que Error Boundary lo maneje
throw new Error('Error al crear usuario')
}
}
Para inesperados, haz throw y deja que burbujee hasta error.tsx.
Errores al obtener datos
En Server Components suelo hacerlo así:
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
// Error esperado: código de estado de error
if (!res.ok) {
if (res.status === 404) {
return { posts: [], error: 'Sin datos por ahora' }
}
// Error de servidor: lanzar para Error Boundary
throw new Error('Error al obtener datos')
}
return { posts: await res.json() }
}
export default async function PostsPage() {
const { posts, error } = await getPosts()
if (error) {
return <div>Sin artículos por ahora</div>
}
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
«Sin datos» no necesita página de error; solo fallos reales del sistema activan la UI de error.tsx.
Utilidad de error.digest
Next.js 15 añade digest, un identificador único generado automáticamente.
Escenario: el usuario ve error, hace captura y escribe a soporte. Con ese digest localizas la petición, hora y error en logs.
En error.tsx:
'use client'
export default function Error({ error }: { error: Error & { digest?: string }}) {
return (
<div>
<h2>Algo salió mal</h2>
<p>Código de error: {error.digest}</p>
<p>Contacta a soporte e indica el código anterior</p>
</div>
)
}
Con Sentry u otra plataforma, digest multiplica la eficiencia del rastreo.
Mejores prácticas en producción
Cómo usarlo bien: lecciones de errores propios.
1. Límites de error granulares
No basta con un error.tsx en la raíz. Zonas críticas merecen su propio límite.
Ejemplo en e-commerce:
app/
├── error.tsx # Red general
├── (shop)/
│ ├── products/
│ │ └── error.tsx # Fallo en listado no tumba el resto
│ ├── cart/
│ │ └── error.tsx # Carrito caído, sigues navegando
│ └── checkout/
│ └── error.tsx # Checkout crítico, manejo aparte
Si el carrito falla, el usuario puede seguir viendo productos.
2. Monitorización y reporte
useEffect en error.tsx es el momento ideal:
'use client'
import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'
export default function Error({ error, reset }: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
Sentry.captureException(error, {
tags: {
errorDigest: error.digest,
errorBoundary: 'app-root'
},
extra: {
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
}
})
}, [error])
return (
// UI de error...
)
}
Incluye error.digest y contexto del usuario para reproducir.
Algunos equipos guardan las últimas 5 URLs visitadas; ayuda mucho al diagnóstico.
3. UI de error amigable
A los técnicos les gusta el stack; al usuario le importa:
- ¿Qué pasó? (lenguaje claro)
- ¿Puedo hacer algo? (acciones concretas)
- ¿Perdí mis datos? (alcance del impacto)
Ejemplo de buena UI:
return (
<div className="error-container">
<h2>No se pudo cargar la página</h2>
<p>Puede ser la red o que nuestro servidor esté descansando un momento</p>
<div className="actions">
<button onClick={reset}>Reintentar</button>
<a href="/">Volver al inicio</a>
<a href="/help">Contactar soporte</a>
</div>
<details className="error-details">
<summary>Información técnica (opcional)</summary>
<code>{error.digest}</code>
</details>
</div>
)
Tono relajado, menos ansiedad. «El servidor descansa» suena mejor que «500 Internal Server Error».
4. Estrategia de reintento inteligente
Además del límite de intentos:
- Retraso: espera 1–2 s antes de reset, da tiempo al servidor
- Backoff exponencial: 1 s, 2 s, 4 s…
- Por tipo: red → reintentar; bug de código → contactar soporte
const [retryCount, setRetryCount] = useState(0)
const [isRetrying, setIsRetrying] = useState(false)
const handleReset = async () => {
setIsRetrying(true)
setRetryCount(prev => prev + 1)
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, retryCount) * 1000)
)
setIsRetrying(false)
reset()
}
5. Diferencias por entorno
Desarrollo y producción no deben mostrar lo mismo:
const isDev = process.env.NODE_ENV === 'development'
return (
<div>
<h2>{isDev ? error.message : 'Algo salió mal'}</h2>
{isDev && (
<pre>
<code>{error.stack}</code>
</pre>
)}
{!isDev && (
<p>Registramos el problema y lo corregiremos pronto</p>
)}
</div>
)
Desarrollo: stack completo. Producción: mensaje amigable, sin filtrar detalles.
6. No abusar
Error Boundary es red de seguridad, no el manejo principal.
Errores esperados → try-catch. Degradación local → no dispares página de error entera.
Avatar que no carga: muestra uno por defecto; no hace falta tumbar todo el perfil.
Reserva Error Boundary para lo verdaderamente inesperado e imposible de aislar.
Conclusión
En resumen, tres ideas:
Primero, Error Boundary no es opcional. La pérdida por pantalla en blanco es peor de lo que crees. Configurarlo bien evita muchas noches de guardia.
Segundo, capas importan. error.tsx para local, global-error.tsx para el resto; en Server Components separa esperado e inesperado. Lo que debes manejar explícitamente, hazlo; lo demás, déjalo burbujear.
Tercero, la experiencia de usuario primero. Detalles técnicos a la monitorización; al usuario, mensajes claros y acciones. El botón «Reintentar» resuelve cerca del 40% de errores transitorios: muy buen retorno.
Añade error.tsx a tu proyecto Next.js. Empieza en la raíz y extiende a zonas críticas. Con Sentry u otra herramienta verás la estabilidad mejorar.
Y no olvides global-error.tsx. Se usa poco, pero es el cinturón de seguridad: no quieres necesitarlo, pero debe estar ahí.
Implementar Error Boundary en Next.js
Añade límites de error a tu aplicación Next.js para gestionar errores en runtime con elegancia
- 1
Step 1: Crear el archivo error.tsx
Crea error.tsx en el directorio app o en cualquier directorio de ruta, con la directiva 'use client' - 2
Step 2: Implementar el componente de manejo de errores
Define el componente Error que recibe los parámetros error y reset, y diseña una UI de error amigable - 3
Step 3: Añadir reporte de errores
En useEffect, reporta el error a plataformas de monitorización como Sentry y registra error.digest - 4
Step 4: Implementar reintento inteligente
Añade un botón de reintento con límite de intentos; para errores transitorios, ofrece un mecanismo de recuperación automática - 5
Step 5: Crear global-error.tsx
Crea global-error.tsx en el directorio app como último recurso, con estructura HTML completa - 6
Step 6: Distinguir tipos de error
En Server Components, distingue errores esperados (manejo explícito) de inesperados (Error Boundary)
FAQ
¿Cuál es la diferencia entre error.tsx y global-error.tsx?
¿Por qué error.tsx debe ser un componente cliente?
¿Puede error.tsx capturar errores de Server Components?
¿Cuándo usar try-catch en lugar de Error Boundary?
¿Cómo funciona la función reset()?
12 min de lectura · Publicado el: 6 ene 2026 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Guía completa para personalizar páginas de error 404 y 500 en Next.js: de la implementación técnica al diseño optimizado
Te enseñamos paso a paso a personalizar las páginas de error de Next.js, con ejemplos completos de not-found.tsx, error.tsx y global-error.tsx, mejores prácticas de diseño y soluciones a problemas habituales para mejorar la UX y reducir la tasa de rebote
Parte 33 de 51
Siguiente
Pruebas unitarias en Next.js: guía completa de configuración con Jest + React Testing Library
Configura el entorno de pruebas de Next.js 15 desde cero: configuración de Jest + React Testing Library, pruebas de Client/Server Components, hooks, técnicas de mock y resolución de problemas frecuentes, con ejemplos de código completos.
Parte 35 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario