Skip to content

Remix Catch Boundaries — Handling Expected Errors

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Remix Catch Boundaries. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Remix catch boundaries: handle expected error responses like 404, 403, and 500, differentiate from unexpected errors, and show user-friendly messages.

In this lesson, you'll implement catch boundaries that handle thrown Response objects separately from unexpected errors, providing appropriate UI for each status code.

What You'll Learn

How catch boundaries work with thrown Responses, handle specific status codes, provide contextual error pages, and use them alongside error boundaries.

Why It Matters

Expected errors (page not found, forbidden, rate limited) need different handling than unexpected bugs. Catch boundaries give you control over how each status code is displayed.

Real-World Use

DodaZIP uses catch boundaries to show "Project not found" with a link to the project list for 404s, and "Upgrade your plan" for 403 errors.

flowchart TD
    A[Thrown Response] --> B{Error or Catch?}
    B -->|CatchBoundary| C[404/403/500 UI]
    B -->|ErrorBoundary| D[Unexpected Error]
    style A fill:#121212,color:#fff

Catch Boundary

import { useCatch } from "@remix-run/react";

export function CatchBoundary() {
  const caught = useCatch();
  
  switch (caught.status) {
    case 404:
      return (
        <div>
          <h1>Page Not Found</h1>
          <p>The resource you're looking for doesn't exist.</p>
          <a href="/">Go home</a>
        </div>
      );
    case 403:
      return (
        <div>
          <h1>Access Denied</h1>
          <p>You don't have permission to view this page.</p>
        </div>
      );
    case 401:
      return (
        <div>
          <h1>Unauthorized</h1>
          <p>Please log in to continue.</p>
          <a href="/login">Log in</a>
        </div>
      );
    default:
      return (
        <div>
          <h1>Error {caught.status}</h1>
          <p>{caught.data}</p>
        </div>
      );
  }
}

Catching in Loaders

Throwing expected errors:

export const loader = async ({ params }) => {
  const post = await getPost(params.slug);
  
  if (!post) {
    throw new Response("Post not found", { status: 404 });
  }
  
  if (post.hidden) {
    throw new Response("This post is private", { status: 403 });
  }
  
  return json(post);
};

The catch boundary catches these and renders status-specific UI.

Combining Catch and Error Boundaries

Use both for comprehensive error handling:

export function CatchBoundary() {
  const caught = useCatch();
  // Handle expected errors
}

export function ErrorBoundary() {
  const error = useRouteError();
  // Handle unexpected errors
}

Remix routes the error to the appropriate boundary automatically.

Nested Catch Boundaries

Child routes can have their own catch boundaries:

// dashboard.analytics.tsx
export function CatchBoundary() {
  const caught = useCatch();
  // Only handles errors in the analytics route
}

If the child doesn't have a catch boundary, the parent's catches the error.

Common Mistakes

  1. Using catch boundaries for unexpected errors: Catch boundaries are for thrown Responses. Unexpected errors go to error boundaries.
  2. Not handling specific status codes: A generic fallback for all status codes defeats the purpose. Provide contextual messages per status.
  3. Forgetting to export CatchBoundary: The function must be a named export. Default exports don't work for catch boundaries.
  4. Not updating the root catch boundary: The root's catch boundary is the last resort for all unmatched routes. Customize it for your app.

Practice Questions

  1. What hook accesses caught response data? Answer: useCatch() from @remix-run/react. It returns the thrown Response with status, statusText, and data.

  2. What's the difference between CatchBoundary and ErrorBoundary? Answer: CatchBoundary handles thrown Response objects (expected errors). ErrorBoundary handles unexpected errors (exceptions, bugs).

  3. What happens if no CatchBoundary exists? Answer: The error bubbles to the parent's CatchBoundary or ErrorBoundary. The root boundary handles it as a fallback.

  4. Can a route have both CatchBoundary and ErrorBoundary? Answer: Yes. Export both functions from the same route module. Remix routes errors to the appropriate one.

Challenge

Build a multi-route app where each route handles its own 404 and 403 cases with contextual messages. The parent layout should only catch errors not handled by children.

Mini Project

Create a file management app with catch boundaries: 404 for missing files, 403 for permission-denied files, 413 for files that are too large, and a generic fallback for other errors.

FAQ

Can I throw custom status codes?

: Yes. Throw new Response("message", { status: 429 }) for Rate Limiting or any custom status code.

Do catch boundaries work on the server?

: Yes. Catch boundaries render on the server for initial loads and on the client for navigations.

Can I redirect from a catch boundary?

: Yes. Use <Navigate to="/login" /> or return a redirect from the component.

How do I log caught errors?

: Add logging logic in the CatchBoundary component, similar to error boundaries.

What's Next

Learn about Remix Links and Meta for managing SEO tags and link elements per route.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro