Cambiar tema

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

Easton editorial illustration: API gateway workstation

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:

  1. No omitas ‘use client’: sin esa línea, Next.js fallará
  2. Objeto error: incluye mensaje y stack; digest es nuevo en Next.js 15 para rastreo
  3. 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>
  )
}
40%
Un botón de reintento permite recuperar automáticamente cerca del 40% de los errores transitorios

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:

  1. Fallo al inicializar el layout raíz (p. ej. la librería de estado global)
  2. 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. 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. 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. 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. 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. 5

    Step 5: Crear global-error.tsx

    Crea global-error.tsx en el directorio app como último recurso, con estructura HTML completa
  6. 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?
error.tsx captura errores a nivel de segmento de ruta, pero no puede capturar errores del layout.tsx del mismo nivel. global-error.tsx es el último recurso: puede capturar errores del layout raíz, debe incluir las etiquetas html y body completas, y solo funciona en producción.
¿Por qué error.tsx debe ser un componente cliente?
Porque error.tsx necesita usar hooks de React (como useEffect) para gestionar el estado de error y la lógica de recuperación, y los hooks solo funcionan en componentes cliente. Debes añadir la directiva 'use client' al inicio del archivo.
¿Puede error.tsx capturar errores de Server Components?
Sí. Next.js transmite la información de error del servidor al cliente y activa el error.tsx más cercano. En producción, los mensajes de error se sanitizan para evitar filtrar información sensible del servidor.
¿Cuándo usar try-catch en lugar de Error Boundary?
Los errores de negocio esperados (validación de formularios, API que devuelve 404, permisos insuficientes) deben manejarse explícitamente con try-catch. Error Boundary debe reservarse para errores inesperados (bugs de código, fallo de conexión a base de datos, caída de servicios de terceros).
¿Cómo funciona la función reset()?
reset() vuelve a renderizar el subárbol de componentes dentro del límite de error. Sirve para errores transitorios (timeout de red, fallo al cargar recursos) donde un reintento puede funcionar. Para bugs de código, reintentar no ayuda: hay que corregir el código y desplegar.

12 min de lectura · Publicado el: 6 ene 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog