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
errorfrom hooks:const { data, error } = useGetClientsQuery()
Not-found pages
Each app provides:
app/[lang]/not-found.tsx— locale-aware 404 pageapp/[lang]/[...404]/page.tsx— catch-all for unmatched routes
ESLint rules
no-consoleis set to error — use@numa/utils/loggerinstead ofconsole.log- Only
packages/utils/logger.tshas an ESLint disable forno-console
Best practices
- Use
toast.error()for user-facing mutation failures - Use error boundaries for unexpected rendering errors
- Use
loggerfor development debugging (stripped in production) - Always provide
onErrorcallbacks in mutation hooks - For API errors, extract the message from
error.response?.data?.message