mcpField
R
remix-v2-error-boundaries-review

remix-v2-error-boundaries-review

github.com/existential-birds测试与质量8541 次安装11 周前更新
代码仓库

关于 remix-v2-error-boundaries-review

面向 TypeScript Remix v2 项目的错误边界代码审查技能,适用于检查路由错误处理和 v1 迁移遗留问题。它涵盖边界结构、异常响应、根边界及错误报告规则。

主要功能

  1. 01检查 ErrorBoundary 导出与错误收窄
  2. 02审查 4xx 和 5xx 的抛出方式
  3. 03检查根边界文档结构
  4. 04识别 CatchBoundary 等 v1 遗留
  5. 05提供误报豁免与严重级别指引

适用场景

  1. 01审查 Remix 路由的错误边界
  2. 02检查 4xx 响应是否触发边界
  3. 03排查 Remix v1 错误处理遗留

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill remix-v2-error-boundaries-review

安装后在 Claude Code 中输入/remix-v2-error-boundaries-review确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
boundary-shape.md
root-boundary.md
throw-response.md
v1-holdovers.md

详细介绍

Remix v2 错误边界代码审查

适用于从 @remix-run/* 导入内容的 TypeScript 路由模块。此主题没有相关的知识技能;下文内联概述了核心思维模型,详情见 references/。

v2 边界模型(请先阅读)

Remix v2 将 v1 的 CatchBoundary 和 ErrorBoundary 合并为单个 ErrorBoundary 路由模块导出。框架会在抛出 Response(例如 throw new Response(...)、throw json(...))和抛出运行时错误(loader/action/render 异常)时调用它。在边界中,使用 useRouteError() 钩子读取错误值,然后按以下顺序缩小类型范围:

  1. isRouteErrorResponse(error) → 这是抛出的 Response;读取 error.status、error.statusText、error.data。
  2. error instanceof Error → 这是实际的运行时错误;读取 error.message。
  3. 否则 → 这是未知的抛出值;渲染通用回退内容。

边界不接受任何 props。CatchBoundary、useCatch 和 future.v2_errorBoundary 标志均已移除——发现其中任何一个,都说明代码仍沿用 v1 的旧写法。错误会由最近的 ErrorBoundary 渲染;如果没有,则向上冒泡至根边界。根边界会重新挂载整个文档,因此必须渲染 <Meta />、<Links /> 和 <Scripts />。只有抛出的 loader/action 结果才会到达边界——return json(...) 即使状态码为 4xx,也属于成功的 loader,而不是错误。服务器端运行时错误还会流经可选的 entry.server.tsx handleError 导出(抛出的 Response 不会)。

快速参考

问题类型参考
缺少路由 ErrorBoundary、边界带 props、仅用 instanceof Error 缩小类型范围、仅用 isRouteErrorResponse 缩小类型范围references/boundary-shape.md
4xx/5xx 该抛出却用了 return、忽略 error.data、抛出字符串、缺少 handleErrorreferences/throw-response.md
缺少根边界、根边界未包含 <Meta />/<Links />/<Scripts />、在根边界中使用 useLoaderData()references/root-boundary.md
导出 CatchBoundary、导入 useCatch、使用 v2_errorBoundary future 标志references/v1-holdovers.md

审查清单

  • ErrorBoundary 声明为 export function ErrorBoundary(),且不带 props
  • 通过 useRouteError() 读取错误,而不是使用 useCatch() 或 prop
  • 类型缩小检查先执行 isRouteErrorResponse(error),然后是 error instanceof Error,最后提供回退处理
  • 安全地渲染 error.data(放入 JSX 前先进行类型标注/缩小)
  • loader/action 中的 4xx/5xx 使用 throw(而不是 return)抛出 Response / json
  • 可能抛出错误的路由应导出自己的 ErrorBoundary(不要因一个组件失败而拆除父级)
  • 根目录 app/root.tsx 导出一个渲染 <Meta />、<Links /> 和 <Scripts /> 的 ErrorBoundary
  • 根边界读取根数据时使用 useRouteLoaderData("root")(而不是 useLoaderData())
  • 任何位置都没有导出 CatchBoundary;没有导入 useCatch;remix.config.js 中没有 future.v2_errorBoundary
  • entry.server.tsx 导出 handleError,并将运行时错误传递给错误报告工具
  • handleError 不应假设抛出的 Response 会经过它(实际上不会)
  • 抛出的值应为 Response/json/Error 实例,绝不能是普通字符串或 POJO

有效模式(不要标记)

以下是正确的 Remix v2 用法,不应报告为问题:

  • 有意从父级继承、但未设置 ErrorBoundary 的路由 — 边界会逐级向上查找。子路由可以省略 ErrorBoundary,由父级(或根路由)渲染回退界面。只有当该路由处理了与用户相关的特定错误体验,且父级边界无法处理时,才应标记。
  • 在 loader/action 中使用 throw new Response(...) 或 throw json(...) — 这是表示 404/401/403 等状态的标准方式。这不是“将异常用于控制流”;这是文档明确规定的 v2 契约。
  • 仅使用 isRouteErrorResponse(error) 进行类型收窄 — 如果路由明确只会抛出 Response,且不存在渲染期间崩溃的风险,这样做可以接受。严重性最高为 ADVISORY;建议添加 instanceof Error 分支以加强防御,但不要将其标记为 Bug。
  • 不调用 useRouteError() 的 ErrorBoundary — 如果边界有意渲染静态的“出了点问题”回退界面(例如不希望显示错误详情的营销页面),这是有效用法。
  • 根 ErrorBoundary 调用 useRouteLoaderData("root") 并得到 undefined — 这是文档所述的防御性模式(根 loader 可能已抛出错误)。不要将对 undefined 的处理标记为“死代码”。
  • handleError 在 request.signal.aborted 时提前返回 — 这是文档所述的噪声过滤机制,并非吞掉错误。
  • handleError 未处理抛出的 Response — 根据框架契约,handleError 只会针对运行时错误触发。未处理 Response 是正确的,并非缺漏。
  • 嵌套的 ErrorBoundary 直接返回片段(不含 <html> / <body>) — 只有根边界负责整个文档。嵌套边界在父级布局内部渲染,不得包含文档标签。

严重性指南

除非代码库已明确规定其他等级,否则请使用以下默认值:

模式默认严重性
v2 代码库中导出 CatchBoundary 或导入 useCatchBLOCKER(会导致构建失败或产生死代码)
根 ErrorBoundary 缺少 <Scripts />BLOCKER(错误页面无路可走)
ErrorBoundary 使用 ({ error }) 这一 v1 属性签名WARN(运行时会静默得到 undefined)
4xx 状态使用 return json(...) 而非 throwWARN(边界永远不会触发)
有渲染崩溃风险的路由缺少 instanceof Error 分支WARN
仅抛出 Response 的路由缺少 instanceof Error 分支ADVISORY
根边界中使用 useLoaderData()(而非 useRouteLoaderData)WARN(可能导致循环)
entry.server.tsx 中缺少 handleErrorADVISORY(可观测性缺口,并非 Bug)

硬性门槛(撰写问题报告前)

按顺序执行。在本批次的所有门槛都通过之前,不要起草面向用户的问题报告。

  1. 位置证据 — 通过: 每个问题都列出路由模块的仓库路径(或 app/root.tsx、app/entry.server.tsx),以及你实际读取的文件中的行号范围或简短原文引述(不能凭记忆,也不能只根据差异猜测)。“根边界有误”如果没有提供 app/root.tsx 的路径,则不能报告。

  2. 豁免检查 — 通过: 对每个问题,你都能用一行说明它为何不属于 有效模式(不要标记)的范围。尤其要确认:缺少 ErrorBoundary 并非有意将错误逐级传递给父级边界;仅使用 isRouteErrorResponse 缩窄类型的路由,并非已明确只会抛出 Response 的路由(若确实如此,应降级为 ADVISORY)。

  3. v1 与 v2 标记检查 — 通过: 撰写问题报告前,使用 grep 检查路由模块(对于横切问题,则检查整个仓库)是否包含:CatchBoundary、useCatch、v2_errorBoundary、ErrorBoundary({ error、ErrorBoundary({error。如果出现其中任意一项,该问题就是 v1 遗留项(加载 references/v1-holdovers.md),必须如此标记——不能将其笼统地标记为“缺少错误处理”问题。如果均未出现,则代码符合 v2 结构,问题应针对 v2 的正确性。

  4. 流程 — 通过: 已针对本次审查完成 审查前验证清单。

审查问题

  1. 每个可能抛出错误的路由(loader、action 或渲染)是否都在正确层级设置了 ErrorBoundary——恢复 UI 重要时使用局部边界,有意逐级传递时使用父级或根级边界?
  2. 每个 ErrorBoundary 是否都调用 useRouteError()(而非 useCatch() 或 props),并首先使用 isRouteErrorResponse 缩窄类型?
  3. 4xx / 5xx 控制流是否使用 throw(而非 return),以确保触发边界?
  4. app/root.tsx 是否导出了带有 <Meta />、<Links /> 和 <Scripts /> 的 ErrorBoundary,并以防御性方式使用 useRouteLoaderData("root")?
  5. 是否还存在 v1 标记(CatchBoundary、useCatch、v2_errorBoundary、({ error }) 属性签名)?
  6. entry.server.tsx 中是否存在 handleError,以便观察运行时错误,且符合正确的契约(不处理 Response)?

补充文档

评论

支持 Markdown