Skip to Content
Error Handling

Error Handling

Numa uses multiple layers of error handling: Next.js error boundaries, provider-level failures, toast notifications, and API error interceptors.

Next.js error boundaries

Each app has error boundaries at two levels:

Root error boundary (app/error.tsx)

Catches errors when even the i18n context is unavailable. Wraps GErrorPage in a NextIntlClientProvider with English messages as fallback:

export default function RootError({ error, reset }) { return ( <NextIntlClientProvider locale="en" messages={enMessages}> <GContainer> <GErrorPage error={error} reset={reset} /> </GContainer> </NextIntlClientProvider> ); }

Locale-scoped error boundary (app/[lang]/error.tsx)

Catches errors within the locale context, where useTranslations is available:

export default function LangError({ error, reset }) { return ( <GContainer> <GErrorPage error={error} reset={reset} /> </GContainer> ); }

@numa/ui-error

A lightweight package providing GErrorPage and not-found components using useTranslations. Used by both apps and providers:

import { GErrorPage } from "@numa/ui-error/error"; import { GNotFound } from "@numa/ui-error/not-found";

Provider-level failures

UserEssentialsRunner in @numa/providers shows GErrorPage when the essentials fetch errors:

// When /me fails and can't be recovered <GErrorPage message={t("errors.noEssentials")} />

Toast notifications

Numa uses sonner for toast notifications:

import { toast } from "@numa/ui/shared/ui/sonner"; // Success toast.success("Client created successfully"); // Error toast.error("Failed to create client. Please try again."); // In mutation hooks useCreateClientMutation({ onSuccess: () => toast.success("Created"), onError: (error) => { const message = error.response?.data?.message ?? "Something went wrong"; toast.error(message); }, });

The Toaster component is mounted in GProviders so toasts work everywhere.

API error handling

Axios interceptor

The response interceptor in @numa/api/axios-instance handles:

  • 401 Unauthorized → attempts silent token refresh
  • Refresh failure → clears cookies, redirects to login
  • Other errors pass through to React Query

React Query defaults

Set in @numa/api/query-client:

  • Queries retry 2 times before showing an error
  • Mutations retry 1 time
  • Components receive error from hooks: const { data, error } = useGetClientsQuery()

Not-found pages

Each app provides:

  • app/[lang]/not-found.tsx — locale-aware 404 page
  • app/[lang]/[...404]/page.tsx — catch-all for unmatched routes

ESLint rules

  • no-console is set to error — use @numa/utils/logger instead of console.log
  • Only packages/utils/logger.ts has an ESLint disable for no-console

Best practices

  1. Use toast.error() for user-facing mutation failures
  2. Use error boundaries for unexpected rendering errors
  3. Use logger for development debugging (stripped in production)
  4. Always provide onError callbacks in mutation hooks
  5. For API errors, extract the message from error.response?.data?.message
Last updated on