ScaleRocket/Web

Error Handling

Error boundaries, toast notifications, Edge Function error patterns, and logging strategies.

Overview

ScaleRocket handles errors at multiple layers: React error boundaries for crashes, toast notifications for user-facing feedback, structured error handling in Edge Functions, and environment-aware logging.

Error Boundaries

error.tsx

Next.js automatically catches rendering errors when you create an error.tsx file:

// src/app/error.tsx
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center">
      <h2 className="text-2xl font-bold">Something went wrong</h2>
      <p className="mt-2 text-gray-500">{error.message}</p>
      <button onClick={reset} className="mt-4 rounded bg-indigo-600 px-4 py-2 text-white">
        Try again
      </button>
    </div>
  );
}

The reset function re-renders the segment, allowing recovery without a full page reload.

not-found.tsx

Handle 404 errors with a custom page:

// src/app/not-found.tsx
export default function NotFound() {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center">
      <h2 className="text-4xl font-bold">404</h2>
      <p className="mt-2 text-gray-500">Page not found.</p>
      <a href="/" className="mt-4 text-indigo-600 hover:underline">
        Go home
      </a>
    </div>
  );
}

Toast Notifications

For non-fatal errors (failed API calls, validation errors), use the toast system instead of error boundaries:

import { useToast } from "@saas/ui";

const { toast } = useToast();

try {
  await updateProfile(data);
  toast({ title: "Profile updated" });
} catch (err) {
  toast({
    title: "Update failed",
    description: err instanceof Error ? err.message : "Please try again.",
    variant: "destructive",
  });
}

This keeps the user on the current page while showing feedback.

Edge Function Error Patterns

Structure Edge Function responses with consistent error shapes:

// supabase/functions/my-function/index.ts
try {
  const result = await processRequest(req);
  return new Response(JSON.stringify({ data: result }), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
} catch (error) {
  return new Response(
    JSON.stringify({ error: error.message || "Internal error" }),
    { status: 500, headers: { "Content-Type": "application/json" } }
  );
}

Common Supabase Error Codes

CodeMeaningHandling
23505Unique constraint violationShow "already exists" message
42501RLS policy violationShow "not authorized" message
PGRST116Row not foundShow 404 or "not found" message
23503Foreign key violationShow "related record required" message

Logging Strategy

Development

Log errors with full stack traces for debugging:

if (process.env.NODE_ENV === "development") {
  console.error("Full error:", error);
  console.error("Stack:", error.stack);
}

Production

In production, avoid leaking internal details to the console. Use structured logging or send errors to a monitoring service:

if (process.env.NODE_ENV === "production") {
  // Send to error tracking service (Sentry, LogRocket, etc.)
  captureException(error);
}

Never expose raw database error messages, stack traces, or internal paths to end users.

Best Practices

  • Use error boundaries for unexpected crashes; use toasts for expected failures.
  • Always provide a recovery action (retry button, link to home).
  • Validate inputs before making API calls to reduce error frequency.
  • Return user-friendly messages; log technical details server-side.

Done reading? Mark this page as complete.

On this page