← 返回
未分类 中文

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 holdov...
Reviews Remix v2 error-handling code for the unified ErrorBoundary, isRouteErrorResponse narrowing, throw-vs-return, root boundary scaffolding, and v1 holdov...
anderskev anderskev 来源
未分类 clawhub v1.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 216
下载
💾 1
安装
1
版本
#latest

概述

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.

  1. error instanceof Error → real runtime error; read error.message.
  2. 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 , , and . 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 //, 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 , , and
  • [ ] 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 / ) — 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 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 evidencePass: 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.

  1. Exemption checkPass: 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).

  1. v1-vs-v2 marker checkPass: 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.

  1. ProtocolPass: 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?

  1. Does each ErrorBoundary call useRouteError() (not useCatch(),

not props) and narrow isRouteErrorResponse first?

  1. Are 4xx / 5xx control flows using throw (not return) so the

boundary actually fires?

  1. Does app/root.tsx export an ErrorBoundary with ,

, and , and use useRouteLoaderData("root")

defensively?

  1. Are there any v1 markers left (CatchBoundary, useCatch,

v2_errorBoundary, ({ error }) prop signature)?

  1. Is handleError present in entry.server.tsx for runtime-error

observability, with the correct contract (no Response handling)?

Additional Documentation

  • Reviewing the ErrorBoundary export shape, hook usage, or narrowing → references/boundary-shape.md
  • Reviewing thrown Response / json patterns, handleError, or return-vs-throw → references/throw-response.md
  • Reviewing app/root.tsx boundary scaffolding → references/root-boundary.md
  • Detecting v1 holdovers (CatchBoundary, useCatch, v2_errorBoundary) → references/v1-holdovers.md
  • Remix v2 ErrorBoundary docs: https://remix.run/docs/en/main/route/error-boundary
  • Remix v2 error handling guide: https://remix.run/docs/en/main/guides/errors
  • Remix v2 entry.server / handleError docs: https://remix.run/docs/en/main/file-conventions/entry.server

版本历史

共 1 个版本

  • v1.0.0 当前
    2026-05-23 23:45 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

dev-programming

CodeConductor.ai

larsonreever
AI驱动平台,提供快速全栈开发、智能体、工作流自动化及低代码AI集成的可扩展产品创建。
★ 80 📥 182,939
dev-programming

Mcporter

steipete
使用 mcporter CLI 直接列出、配置、认证及调用 MCP 服务器/工具(支持 HTTP 或 stdio),涵盖临时服务器、配置编辑及 CLI/类型生成功能。
★ 198 📥 68,169
dev-programming

Github

steipete
使用 `gh` CLI 与 GitHub 交互,通过 `gh issue`、`gh pr`、`gh run` 和 `gh api` 管理议题、PR、CI 运行及高级查询。
★ 686 📥 330,799