We use tracking cookies to understand how you use the product and help us improve it. For more information on how we store cookies, read our  privacy policy.

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

Field Validation
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

Set Error from API
catch (err: any) {
  setError('email', {
    type: 'manual',
    message: 'This email is already registered',
  });
}

API Error Handling

Clerk API Errors

Clerk Error Pattern
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

Stripe Error Handling
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

@/app/api/example/route.ts
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

API Validation
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

Try-Catch Pattern
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

@/app/(base)/error.tsx
'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

@/app/(base)/not-found.tsx
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

Toast Examples
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

Prisma Error Handling
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

Skeleton Component
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>
  );
}
Conditional Rendering
import { BaseFormSkeleton } from '@/components/user/BaseFormSkeleton';

export function SignInForm() {
  const { isLoaded } = useSignIn();

  if (!isLoaded) {
    return <BaseFormSkeleton />;
  }

  return <form>{/* Form content */}</form>;
}

How is this guide ?

Last updated on