本文へ移動
cccskills
無料GitHub で公開

remix-v2-error-boundaries-review

Reviews Remix v2 error-handling code for the unified ErrorBoundary, isRouteErrorResponse narrowing, throw-vs-return, root boundary scaffolding, and v1 holdovers (CatchBoundary, useCatch). Use when reviewing routes that throw or define ErrorBoundary in a Remix v2 codebase.

インストール方法を見る

含まれるファイル(5)

  • SKILL.md9.5 KB
  • references/boundary-shape.md6.1 KB
  • references/root-boundary.md6.4 KB
  • references/throw-response.md7.1 KB
  • references/v1-holdovers.md6.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Remix v2 Error Boundaries Code Review

Targets TypeScript route modules importing from @remix-run/*. No sibling knowledge skill exists for this topic; the canonical mental model is summarized inline below and expanded in references/.

v2 Boundary Model (read first)

Remix v2 unified v1's CatchBoundary + ErrorBoundary into a single ErrorBoundary route-module export. The framework calls it for both thrown Responses (e.g. throw new Response(...), throw json(...)) and thrown runtime errors (loader/action/render exceptions). Inside the boundary you read the value with the useRouteError() hook, then narrow in this order:

  1. isRouteErrorResponse(error) → it was a thrown Response; read error.status, error.statusText, error.data.
  2. error instanceof Error → real runtime error; read error.message.
  3. else → unknown thrown value; render a generic fallback.

The boundary takes no props. CatchBoundary, useCatch, and the future.v2_errorBoundary flag are all gone — finding any of them is a v1 holdover. Errors render the nearest ErrorBoundary and bubble to the root if none exists; the root boundary remounts the whole document, so it must render <Meta />, <Links />, and <Scripts />. Only thrown loader/action results reach the boundary — a return json(...) with a 4xx status is a successful loader, not an error. Server-side runtime errors also flow through an optional entry.server.tsx handleError export (thrown Responses do not).

Quick Reference

Issue TypeReference
Missing route ErrorBoundary, props-on-boundary, narrowing-only instanceof Error, narrowing-only isRouteErrorResponsereferences/boundary-shape.md
Return-instead-of-throw 4xx/5xx, swallowing error.data, throwing strings, missing handleErrorreferences/throw-response.md
Missing root boundary, root boundary without <Meta />/<Links />/<Scripts />, useLoaderData() in root boundaryreferences/root-boundary.md
CatchBoundary export, useCatch import, v2_errorBoundary future flagreferences/v1-holdovers.md

Review Checklist

  • ErrorBoundary declared export function ErrorBoundary() with no props
  • Error read via useRouteError(), not useCatch() and not a prop
  • Narrowing checks isRouteErrorResponse(error) first, then error instanceof Error, then fallback
  • error.data rendered defensively (typed/narrowed before going into JSX)
  • 4xx / 5xx in loaders/actions use throw (not return) for Response / json
  • Routes that can throw export their own ErrorBoundary (don't tear down parents for a widget failure)
  • Root app/root.tsx exports an ErrorBoundary that renders <Meta />, <Links />, and <Scripts />
  • Root boundary uses useRouteLoaderData("root") (not useLoaderData()) when reading root data
  • No CatchBoundary export anywhere; no useCatch import; no future.v2_errorBoundary in remix.config.js
  • entry.server.tsx exports handleError and pipes runtime errors to an error reporter
  • handleError does not assume thrown Responses flow through it (they don't)
  • Thrown values are Response/json/Error instances — never plain strings or POJOs

Valid Patterns (Do NOT Flag)

These are correct Remix v2 usage and must not be reported as issues:

  • Route without ErrorBoundary that intentionally inherits from a parent — Boundaries cascade up. A child route may omit ErrorBoundary so the parent (or root) renders the fallback. Only flag if the route handles user-distinct error UX and a parent boundary cannot.
  • throw new Response(...) or throw json(...) from a loader/action — The canonical way to signal 404/401/403/etc. This is not "using exceptions for control flow"; it is documented v2 contract.
  • Narrowing only with isRouteErrorResponse(error) — Acceptable when the route demonstrably only throws Responses and has no render-time crash risk. Severity is ADVISORY at most; suggest adding an instanceof Error branch for defense-in-depth, do not flag as a bug.
  • ErrorBoundary that does not call useRouteError() — Valid when the boundary renders a static "Something went wrong" fallback intentionally (e.g. marketing pages that don't want to surface error detail).
  • Root ErrorBoundary calling useRouteLoaderData("root") and getting undefined — Documented defensive pattern (root loader may have thrown). Do not flag the undefined handling as "dead code."
  • handleError returning early on request.signal.aborted — Documented noise filter, not a swallowed error.
  • handleError not handling thrown Responses — By framework contract handleError only fires for runtime errors. The absence of Response handling is correct, not a gap.
  • Nested ErrorBoundary returning a bare fragment (no <html> / <body>) — Only the root boundary owns the document. Nested boundaries render inside parent layouts and must not include document tags.

Severity guidance

Use these defaults unless the codebase has documented a different scale:

PatternDefault severity
CatchBoundary export or useCatch import in v2 codebaseBLOCKER (build-breaking or dead code)
Root ErrorBoundary missing <Scripts />BLOCKER (dead-end error page)
ErrorBoundary with ({ error }) v1 prop signatureWARN (silent runtime undefined)
return json(...) for 4xx instead of throwWARN (boundary never fires)
Missing instanceof Error branch on a route with render-crash riskWARN
Missing instanceof Error branch on a Response-only routeADVISORY
useLoaderData() (vs useRouteLoaderData) in root boundaryWARN (latent loop)
Missing handleError in entry.server.tsxADVISORY (observability gap, not a bug)

Hard gates (before writing findings)

Run in order. Do not draft user-facing findings until every gate passes for the batch you are about to report.

  1. Location evidence — Pass: Each issue lists the repo path to the route module (or app/root.tsx, or app/entry.server.tsx) and either a line range or a short verbatim quote from the file you read (not from memory or diff-only guesswork). "The root boundary is wrong" without a path to app/root.tsx is not reportable.

  2. Exemption check — Pass: For each issue, you can state in one line why it is not covered by Valid Patterns (Do NOT Flag). In particular: confirm a missing ErrorBoundary is not a deliberate cascade to a parent boundary; confirm an isRouteErrorResponse-only narrowing is not on a route that demonstrably only throws Responses (downgrade to ADVISORY in that case).

  3. v1-vs-v2 marker check — Pass: Before writing the finding, grep the route module (and the repo at large for cross-cutting issues) for: CatchBoundary, useCatch, v2_errorBoundary, ErrorBoundary({ error, ErrorBoundary({error. If any of these appear, the finding is a v1 holdover (load references/v1-holdovers.md) and must be labeled as such — not as a generic "missing error handling" issue. If none appear, the code is v2-shape and the finding is about v2 correctness.

  4. Protocol — Pass: You completed the Pre-Report Verification Checklist in review-verification-protocol for this review.

Review Questions

  1. Does every route that can throw (loader, action, or render) have an ErrorBoundary at the right level — local where the recovery UI matters, parent/root where cascade is intentional?
  2. Does each ErrorBoundary call useRouteError() (not useCatch(), not props) and narrow isRouteErrorResponse first?
  3. Are 4xx / 5xx control flows using throw (not return) so the boundary actually fires?
  4. Does app/root.tsx export an ErrorBoundary with <Meta />, <Links />, and <Scripts />, and use useRouteLoaderData("root") defensively?
  5. Are there any v1 markers left (CatchBoundary, useCatch, v2_errorBoundary, ({ error }) prop signature)?
  6. Is handleError present in entry.server.tsx for runtime-error observability, with the correct contract (no Response handling)?

Additional Documentation

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when you need to mine a conversation, session transcript, or design discussion for architectural decisions before writing ADRs. Identifies problem-solution pairs, trade-off debates, technology choices, and explicit "[ADR]" tags. Triggers on "what decisions did we make", "extract decisions from this chat", "find the choices in our discussion", or "summarize architectural decisions". Also useful after long planning sessions to capture decisions that were made implicitly. Does NOT write ADR documents — use adr-writing or write-adr for that.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when writing or formatting an ADR document using the MADR template, applying Definition of Done (E.C.A.D.R.) criteria, or verifying ADR completeness. Triggers on "write the ADR", "format as MADR", "check ADR quality", "mark gaps in ADR". Also triggers when a decision has been extracted and needs to become a document. Does NOT extract decisions from conversations (use adr-decision-extraction) or orchestrate the full extract-confirm-write workflow (use write-adr).

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when auditing an agent codebase against the 12-Factor Agents methodology, reviewing LLM-powered system architecture, or assessing agentic app compliance. Triggers on "analyze agent architecture", "12-factor audit", "how compliant is this agent", or "evaluate this LLM app". Also applies when comparing frameworks or planning agent improvements. Not for quick checklists — this performs deep per-factor codebase analysis with file-level evidence.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Vercel AI Elements for workflow UI components. Use when building chat interfaces, displaying tool execution, showing reasoning/thinking, or creating job queues. Triggers on ai-elements, Queue, Confirmation, Tool, Reasoning, Shimmer, Loader, Message, Conversation, PromptInput.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Reviews App Intents code for intent structure, entities, shortcuts, and parameters. Use when reviewing code with import AppIntents, @AppIntent, AppEntity, AppShortcutsProvider, or @Parameter.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when the user wants a cited, structured read of local documents and project knowledge. Triggers on: "analyze these docs", "scan my project for context", "read the docs folder", "summarize what's in .beagle/concepts/", "extract context from docs/", "what's in this folder", "go read everything in X and tell me what's there". Also invoked programmatically by other beagle skills (prfaq-beagle Ignition, brainstorm-beagle reference points, strategy-interview context grounding) via the companion contract. Does NOT trigger on codebase lookups ("find this function", "search the repo"), web research (use web-research), LLM-as-judge evaluation (use llm-judge), or document editing (use humanize-beagle). Produces a written scan plan, parallel-subagent findings, and a cited synthesis report on disk — never inline prose, never unsourced claims.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

existential-birds のスキルをすべて見る

このスキルの問題を報告する