Pollito Blog
November 8, 2025

Proyectos de Software Grandes: Manejo de Errores

Posted on November 8, 2025  •  6 minutes  • 1139 words  • Other languages:  English

Este post es parte de mi serie de blog Proyectos de Software Grandes .

Código Fuente

Todos los snippets de código que aparecen en este post están disponibles en la rama dedicada a este artículo en el repo de GitHub del proyecto:

https://github.com/franBec/tas/tree/feature/2025-11-08

Por Qué es Clave Manejar Errores

Un proyecto de software grande tiene que permitir que el usuario se recupere cuando algo inevitablemente sale mal.

Next.js nos da patrones basados en componentes para manejar tanto los errores inesperados en tiempo de ejecución como los fallos de página 404.

Barrera de Errores por Segmento

En el App Router, el archivo error.tsx define una React Error Boundary específica para un segmento de ruta y sus hijos anidados.

Acá tenés una implementación para la barrera de errores de la aplicación de alto nivel:

"use client";

import { startTransition } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { AlertTriangle } from "lucide-react";

import { PageLayout } from "@/components/layout/page-layout";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";

interface NextJsError extends Error {
  digest?: string;
}

const ErrorBoundary = ({
  error,
  reset,
}: {
  error: NextJsError;
  reset: () => void;
}) => {
  const router = useRouter();
  
  // Use startTransition to keep the UI responsive during the refresh operation
  const reload = () => {
    startTransition(() => {
      router.refresh();
      reset();
    });
  };
  return (
    <PageLayout>
      <PageLayout.TwoColumn>
        <PageLayout.LeftColumn>
          <Card>
            <CardHeader>
              <CardTitle className="flex items-center gap-2">
                <AlertTriangle />
                Something went wrong
              </CardTitle>
              <CardDescription>
                We are sorry, but something unexpected happened.
              </CardDescription>
            </CardHeader>
            <CardContent>
              <p className="text-destructive">
                {/* The digest is useful for looking up the error in server logs */}
                {error.digest ? `Error Reference: ${error.digest}` : null}
              </p>
            </CardContent>
            <CardFooter>
              <Button onClick={reload}>Try Again</Button>
            </CardFooter>
          </Card>
        </PageLayout.LeftColumn>
        <PageLayout.RightColumn>
          <Image
            src="/undraw_connection-lost_am29.svg"
            alt="Connection lost illustration"
            width={600}
            height={400}
            className="w-full max-w-lg"
          />
        </PageLayout.RightColumn>
      </PageLayout.TwoColumn>
    </PageLayout>
  );
};
export default ErrorBoundary;

Error boundary

Barrera de Errores Global

Si bien error.tsx agarra los errores dentro de un segmento, no puede atrapar los errores que se tiran en el archivo layout.tsx padre de ese mismo segmento. Esto pasa porque el componente layout está más arriba en el árbol de componentes de React que la boundary en sí.

Para atrapar errores críticos lanzados en el src/app/layout.tsx raíz, Next.js nos da el archivo especial src/app/global-error.tsx.

Acá tenés una implementación:

"use client";

import Image from "next/image";
import { AlertTriangle } from "lucide-react";

import { PageLayout } from "@/components/layout/page-layout";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";

interface NextJsError extends Error {
  digest?: string;
}

const GlobalError = ({ error }: { error: NextJsError }) => {
  return (
    <html>
      <body>
        <PageLayout>
          <PageLayout.Header
            title="Municipal Services"
            subtitle="Your Digital Gateway to Local Government Services"
          />
          <PageLayout.TwoColumn>
            <PageLayout.LeftColumn>
              <Card>
                <CardHeader>
                  <CardTitle className="flex items-center gap-2">
                    <AlertTriangle />
                    Something went wrong
                  </CardTitle>
                  <CardDescription>
                    We are sorry, but something unexpected happened.
                  </CardDescription>
                </CardHeader>
                <CardContent>
                  <p className="text-destructive">
                    {error.digest ? `Error Reference: ${error.digest}` : null}
                  </p>
                </CardContent>
                <CardFooter className="flex gap-4">
                  <Button variant="secondary" asChild>
                    <a href="/">Go to Home</a>
                  </Button>
                </CardFooter>
              </Card>
            </PageLayout.LeftColumn>
            <PageLayout.RightColumn>
              <Image
                src="/undraw_connection-lost_am29.svg"
                alt="Connection lost illustration"
                width={600}
                height={400}
                className="w-full max-w-lg"
              />
            </PageLayout.RightColumn>
          </PageLayout.TwoColumn>
        </PageLayout>
      </body>
    </html>
  );
};
export default GlobalError;

Una Nota sobre la Navegación

Como el estado de la aplicación raíz está completamente roto en un escenario de error global, intentar una navegación suave (usando el <Link> de Next.js) no es confiable. Usamos un enlace de actualización dura estándar (<a href="/">) para forzar un reinicio total de la aplicación.

Dado que este uso de <a> en lugar de <Link> puede generar quilombo con las reglas de linting estándar, debemos agregar src/app/global-error.test.tsx a la lista de ignores en eslint.config.mjs.

Testeando la Barrera Global

Debido a las diferencias fundamentales entre las React Error Boundaries en desarrollo y producción, global-error.tsx solo se activa cuando estás corriendo un build de producción (next build y next start).

Para verificar tu implementación, podrías introducir temporalmente un botón simple que lance un error explícitamente dentro de src/app/layout.tsx, crear un build de producción y ejecutar la aplicación buildeada.

button that throws error

global boundary screen

Página de No Encontrado

El archivo not-found.tsx maneja los errores 404 cuando un usuario navega a una URL que no matchea ninguna ruta definida. Esta página se dispara automáticamente por Next.js cuando ninguna ruta coincide con la URL, o explícitamente al llamar a la función de utilidad notFound() dentro de los componentes de servidor.

Acá está nuestro src/app/not-found.tsx custom:

"use client";

import Image from "next/image";
import Link from "next/link";
import { SearchX } from "lucide-react";

import { PageLayout } from "@/components/layout/page-layout";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";

const NotFound = () => {
  return (
    <PageLayout>
      <PageLayout.TwoColumn>
        <PageLayout.LeftColumn>
          <Card>
            <CardHeader>
              <CardTitle className="flex items-center gap-2">
                <SearchX />
                Page Not Found
              </CardTitle>
              <CardDescription>
                We are sorry, but the page you are looking for does not exist.
              </CardDescription>
            </CardHeader>
            <CardContent>
              <p>
                The link you followed may be broken, or the page may have been
                removed.
              </p>
            </CardContent>
            <CardFooter>
              <Button asChild>
                <Link href="/">Go to Homepage</Link>
              </Button>
            </CardFooter>
          </Card>
        </PageLayout.LeftColumn>
        <PageLayout.RightColumn>
          <Image
            src="/undraw_void_wez2.svg"
            alt="Page not found illustration"
            width={600}
            height={400}
            className="w-full max-w-lg"
          />
        </PageLayout.RightColumn>
      </PageLayout.TwoColumn>
    </PageLayout>
  );
};

export default NotFound;

Not Found Page

¿Qué Sigue?

Nos tomamos un momento para blindar la experiencia del usuario antes de meternos de lleno en funcionalidades complejas. Ahora sí, podemos encarar el próximo desafío arquitectónico, que es crucial y complicado a la vez: Autenticación y Autorización.

Hey, check me out!

You can find me here