All skills
remix-run avatar

/react-router-framework-mode

@220978e
by Remixremix-run/agent-skills136 stars
4

Build full-stack React applications using React Router's framework mode. Use when configuring routes, working with loaders and actions, handling forms, handling navigation, pending/optimistic UI, error boundaries, or working with react-router.config.ts or other react router conventions.

Use this Skill: https://skilld.dev/gh/remix-run/agent-skills/react-router-framework-mode

This session only. Nothing lands on disk.

referenceserror-handling.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Error Handling

React Router catches errors in loaders, actions, and rendering, then displays them in error boundaries.

ErrorBoundary Export

Export an ErrorBoundary from any route module:

import { isRouteErrorResponse, useRouteError } from "react-router";

export function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    return (
      <div>
        <h1>
          {error.status} {error.statusText}
        </h1>
        <p>{error.data}</p>
      </div>
    );
  }

  if (error instanceof Error) {
    return (
      <div>
        <h1>Error</h1>
        <p>{error.message}</p>
        {process.env.NODE_ENV === "development" && <pre>{error.stack}</pre>}
      </div>
    );
  }

  return <h1>Unknown Error</h1>;
}

Throwing Responses

Throw Response objects for expected errors:

export async function loader({ params }: Route.LoaderArgs) {
  const product = await db.getProduct(params.id);

  if (!product) {
    throw new Response("Product not found", { status: 404 });
  }

  return product;
}

Use the data helper for structured error data:

import { data } from "react-router";

export async function loader({ params }: Route.LoaderArgs) {
  const product = await db.getProduct(params.id);

  if (!product) {
    throw data(
      { message: "Product not found", productId: params.id },
      { status: 404 },
    );
  }

  return product;
}

isRouteErrorResponse

Check if an error is a Response thrown from a loader/action:

import { isRouteErrorResponse, useRouteError } from "react-router";

export function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    // This was a Response thrown intentionally
    // error.status, error.statusText, error.data are available
    return <NotFoundPage />;
  }

  // This was an unexpected error
  return <GenericErrorPage />;
}

Error Bubbling

Errors bubble up to the nearest ErrorBoundary:

root.tsx (has ErrorBoundary)
└── products.tsx (no ErrorBoundary)
    └── product.$id.tsx (has ErrorBoundary)
  • Error in product.$id.tsx → caught by product.$id.tsx
  • Error in products.tsx → caught by root.tsx

Root Error Boundary

Always define an ErrorBoundary in your root route as a last resort:

// app/root.tsx
export function ErrorBoundary() {
  const error = useRouteError();

  return (
    <html>
      <head>
        <title>Oops!</title>
      </head>
      <body>
        <h1>Something went wrong</h1>
        {isRouteErrorResponse(error) ? (
          <p>
            {error.status}: {error.data}
          </p>
        ) : (
          <p>An unexpected error occurred</p>
        )}
      </body>
    </html>
  );
}

Error Reporting

Report errors to a logging service in entry.server.tsx:

// app/entry.server.tsx
import type { HandleErrorFunction } from "react-router";

export const handleError: HandleErrorFunction = (error, { request }) => {
  // Don't log aborted requests
  if (request.signal.aborted) {
    return;
  }

  // Report to your error tracking service
  reportErrorToService(error);

  // Always log for debugging
  console.error(error);
};

Reveal the server entry if it doesn't exist:

npx react-router reveal entry.server

Validation Errors (Not Error Boundaries)

For form validation, return errors from the action instead of throwing:

import { data } from "react-router";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const email = formData.get("email")?.toString() ?? "";

  if (!email.includes("@")) {
    return data({ errors: { email: "Invalid email" } }, { status: 400 });
  }

  // Success
  await createUser({ email });
  return redirect("/welcome");
}

Access in component via fetcher.data:

function SignupForm() {
  const fetcher = useFetcher();
  const errors = fetcher.data?.errors;

  return (
    <fetcher.Form method="post">
      <input type="email" name="email" />
      {errors?.email && <span className="error">{errors.email}</span>}
      <button>Sign Up</button>
    </fetcher.Form>
  );
}

Production vs Development

In production, error messages are sanitized to prevent leaking sensitive info. Full error details are only shown in development.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill is a comprehensive documentation reference for building applications with React Router's framework mode. It provides standard architectural patterns, code examples, and best practices for routing, data loading, actions, and session management. No security risks or malicious patterns were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    2/13 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 220978e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Steadyupdated 8 months ago

README badge

README badge for remix-run/agent-skills/react-router-framework-mode