Skip to main content

Error Handling

Next.js error handling differs from React due to its layered architecture. Understanding where errors originate and how they're transformed is key to effective error handling.

Architecture Overview​

Client Component ──► Generated Route ──► Server Function ──► External API
│
(normalizes errors)

Server Component ──────────────────────► Server Function ──► External API
│
(throws on error)
  • Client-side: Hooks call generated API routes, which call server functions, which call external APIs. Errors are normalized by the route layer before reaching the client.
  • Server-side: Server functions can be called directly from Server Components or during SSR. Errors surface as thrown exceptions.

Client-Side Error Handling​

Client-side hooks receive the same error types as the React SDK. When isError(state) is true, state.error is an IntrigError.

Error Types​

All errors surface as HttpError on the client because the route layer transforms exceptions into HTTP responses:

Original ErrorRoute ResponseClient Receives
AxiosError (4xx/5xx)JSON with original statusHttpError with API's status and body
ZodError (validation)400 with { errors: [...] }HttpError with status 400
Unknown exception500 with error detailsHttpError with status 500

Type Guards​

import { isError, isHttpError } from '@intrig/next';

const [state, execute, clear] = useGetEmployee();

if (isError(state) && isHttpError(state.error)) {
const { status, body } = state.error;

if (status === 400 && body?.errors) {
// Validation error from route layer
const fieldErrors = body.errors as Array<{ path: string; message: string }>;
} else if (status === 401) {
// Unauthorized
} else if (status >= 500) {
// Server error
}
}

Validation Error Shape​

When the route layer catches a ZodError, it returns:

{
errors: Array<{
path: string; // Dot-notation field path (e.g., "user.email")
message: string; // Validation message
}>
}

Request Validation​

Like React, the execute function returns a DispatchState for immediate validation feedback:

import { isValidationError, isSuccessfulDispatch } from '@intrig/next';

const result = createEmployee(data, params);

if (isValidationError(result)) {
// Request not sent — validation failed client-side
console.log(result.error);
}

Error Display Component​

import { isError, isHttpError, NetworkState } from '@intrig/next';

function ApiError({ state }: { state: NetworkState<any> }) {
if (!isError(state)) return null;

const { error } = state;
if (!isHttpError(error)) return null;

const { status, body } = error;

// Route-layer validation error
if (status === 400 && Array.isArray(body?.errors)) {
return (
<ul>
{body.errors.map((err: { path: string; message: string }, i: number) => (
<li key={i}>{err.path}: {err.message}</li>
))}
</ul>
);
}

// API error with message
if (body?.message) {
return <p>{body.message}</p>;
}

// Generic HTTP error
return <p>Request failed with status {status}</p>;
}

Server-Side Error Handling​

Server functions throw exceptions on failure. Use standard try/catch patterns.

Error Types​

Server functions throw raw AxiosError from the underlying HTTP client:

import { AxiosError, isAxiosError } from 'axios';

try {
const { data } = await getEmployee({ id });
} catch (e) {
if (isAxiosError(e)) {
const status = e.response?.status;
const body = e.response?.data;
// Handle based on status/body
}
}

Server Component Example​

// app/employees/[id]/page.tsx
import { getEmployee } from '@intrig/next/employee_api/employees/getEmployee';
import { isAxiosError } from 'axios';
import { notFound } from 'next/navigation';

export default async function EmployeePage({ params }: { params: { id: string } }) {
try {
const { data: employee } = await getEmployee({ id: params.id });
return <EmployeeDetails employee={employee} />;
} catch (e) {
if (isAxiosError(e) && e.response?.status === 404) {
notFound();
}
throw e; // Let error boundary handle
}
}

Server Action Example​

'use server';

import { createEmployee } from '@intrig/next/employee_api/employees/createEmployee';
import { isAxiosError } from 'axios';

export async function createEmployeeAction(formData: FormData) {
try {
const { data } = await createEmployee({
name: formData.get('name') as string,
email: formData.get('email') as string,
});
return { success: true, data };
} catch (e) {
if (isAxiosError(e)) {
return {
success: false,
status: e.response?.status,
error: e.response?.data
};
}
return { success: false, error: 'Unknown error' };
}
}

Error Boundary Integration​

For unhandled errors in Server Components, use Next.js error boundaries:

// app/employees/error.tsx
'use client';

export default function EmployeeError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Failed to load employee</h2>
<button onClick={reset}>Retry</button>
</div>
);
}

Client vs Server Comparison​

AspectClient-SideServer-Side
Error sourcestate.error (after route normalization)Thrown exception
Primary typeHttpErrorAxiosError
Validation errorsHttpError with 400 statusThrown before request
Type guardsisHttpError(), etc.isAxiosError()
Recoveryclear() and re-executeRetry in catch block

Imports Summary​

// Client-side (same as React)
import {
isError,
isHttpError,
isValidationError,
isSuccessfulDispatch
} from '@intrig/next';

// Server-side
import { isAxiosError } from 'axios';