Error Handling Patterns
Error handling strategies for forms, API routes, server components, and client interactions.
Error handling in Plainform uses Next.js error boundaries, toast notifications, form validation, and API error responses.
Form Validation Errors
Zod Schema Errors
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Must be at least 8 characters'),
});
<Input
{...register('email')}
errorMessage={errors.email?.message}
isInvalid={!!errors.email}
/>Manual Field Errors
catch (err: any) {
setError('email', {
type: 'manual',
message: 'This email is already registered',
});
}API Error Handling
Clerk API Errors
import { ClerkAPIError } from '@clerk/types';
import { toast } from 'sonner';
catch (err: any) {
err.errors.forEach((error: ClerkAPIError) => {
const field = error.meta?.paramName as keyof FormData;
if (field && error.longMessage) {
setError(field, { message: error.longMessage });
} else {
toast.error(error.message);
}
});
}Stripe API Errors
import Stripe from 'stripe';
catch (error) {
if (error instanceof Stripe.errors.StripeCardError) {
return { error: 'Card was declined' };
} else if (error instanceof Stripe.errors.StripeRateLimitError) {
return { error: 'Too many requests' };
}
return { error: 'Payment failed' };
}API Route Errors
Standard Error Response
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
try {
const body = await req.json();
if (!body.email) {
return NextResponse.json(
{ error: 'Email is required' },
{ status: 400 }
);
}
const result = await processData(body);
return NextResponse.json({ data: result });
} catch (error) {
console.error('API error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}Validation with Zod
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
export async function POST(req: Request) {
try {
const body = await req.json();
const validated = schema.parse(body);
return NextResponse.json({ success: true });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
);
}
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
}
}Server Component Errors
export default async function Page() {
try {
const data = await fetchData();
return <div>{data.title}</div>;
} catch (error) {
console.error('Failed to fetch data:', error);
return <div>Failed to load content</div>;
}
}Error Boundaries
Route Error Boundary
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="flex flex-col items-center gap-4 p-8">
<h2 className="text-2xl font-bold">Something went wrong</h2>
<p className="text-neutral-foreground">{error.message}</p>
<button onClick={reset} className="btn">
Try again
</button>
</div>
);
}Not Found Page
import { NotFoundIllustration } from '@/components/svgs/NotFoundIllustration';
export default function NotFound() {
return (
<div className="flex flex-col items-center gap-4">
<NotFoundIllustration size={200} />
<h2 className="text-2xl font-bold">Page Not Found</h2>
<a href="/" className="btn">Go Home</a>
</div>
);
}Toast Notifications
import { toast } from 'sonner';
// Success
toast.success('Profile updated successfully');
// Error
toast.error('Failed to save changes');
// Custom
toast('Processing...', {
description: 'This may take a few moments',
duration: 5000,
});Database Errors
import { Prisma } from '@prisma/client';
try {
const user = await prisma.user.create({ data });
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
return { error: 'Email already exists' };
}
}
return { error: 'Database error' };
}Loading States
export function BaseFormSkeleton() {
return (
<div className="flex flex-col gap-4 animate-pulse">
<div className="h-10 bg-neutral rounded" />
<div className="h-10 bg-neutral rounded" />
<div className="h-10 bg-neutral rounded" />
</div>
);
}import { BaseFormSkeleton } from '@/components/user/BaseFormSkeleton';
export function SignInForm() {
const { isLoaded } = useSignIn();
if (!isLoaded) {
return <BaseFormSkeleton />;
}
return <form>{/* Form content */}</form>;
}Related
- Next.js Error Handling - Official docs
- Zod Validation - Schema validation
- Sonner - Toast notifications
How is this guide ?
Last updated on