mcpField
R
remix-v2-data-flow-review

remix-v2-data-flow-review

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

关于 remix-v2-data-flow-review

面向 TypeScript Remix v2 项目的代码审查技能,聚焦路由数据加载、提交、重验证与流式数据模式,并提供检查清单和误报豁免规则。

主要功能

  1. 01检查 loader 与 action 数据流
  2. 02审查表单验证和敏感字段泄露
  3. 03检查重验证与待处理状态
  4. 04审查 defer 和流式数据用法
  5. 05提供审查门槛与有效模式

适用场景

  1. 01审查 Remix v2 路由模块的数据处理
  2. 02检查 action 的验证与成功分支
  3. 03排查重验证或延迟数据模式问题

安装

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

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

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

包含的文件

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

references/
actions.md
defer-await.md
loaders.md
revalidation.md

详细介绍

Remix v2 数据流代码审查

针对导入 @remix-run/* 的 TypeScript 路由模块。请参阅 remix-v2-data-flow,了解规范模式。

范围

  • 范围内:app/routes/ 下导出 loader、action、shouldRevalidate 或 headers 的路由模块;使用 useLoaderData、useActionData、useNavigation、useFetcher、useRevalidator、<Await> 的组件。
  • 范围外:表单易用性(<Form> 标记、无障碍性、useFetcher UI 模式)→ 由 remix-v2-forms-review 覆盖。路由模块约定、文件命名、嵌套路由、错误边界位置 → 由 remix-v2-routing-review 覆盖。
  • 预期导入:服务器工具使用 @remix-run/node(或 @remix-run/cloudflare / @remix-run/deno);钩子和组件使用 @remix-run/react。

快速参考

问题类型参考
在 loader 中执行变更、缺少校验、泄露服务器字段、抛出基本类型值、缺少参数检查references/loaders.md
未校验的 FormData、成功时使用 json 而非 redirect、缺少错误分支、泄露 actionDatareferences/actions.md
遗留的 useTransition v1 用法、缺少待处理状态、无差别地设置 shouldRevalidate: false、误用 useRevalidatorreferences/revalidation.md
对已经快速的数据使用 defer、缺少 <Suspense>、<Await> 没有 errorElement、等待本应流式传输的数据references/defer-await.md

审查清单

  • 首次渲染所需的数据由 loader 提供,而非通过 useEffect 获取
  • Loader 只读取数据;写入操作放在 action 中
  • 使用前先校验 request.formData() 的结果(zod/valibot/invariant)
  • Loader/action 的返回值是经过筛选的 DTO——不包含密码哈希、令牌或 internal_* 字段
  • useLoaderData<typeof loader>() 使用类型注解形式(而非 as Foo)
  • 404 / 认证的短路处理使用 throw 抛出 Response(或 json/redirect),绝不使用普通 Error 或字符串
  • 成功的 action 返回 redirect(...)(PRG);校验失败时返回 json({ errors }, { status: 400 })
  • Action 处理成功和错误两种分支;不要静默地 return null
  • 使用前通过 invariant / zod 检查 params.foo
  • 待处理 UI 读取 useNavigation() / fetcher.state——不使用 useTransition
  • formMethod 比较使用大写形式("POST",而非 "post")
  • shouldRevalidate 默认返回 defaultShouldRevalidate;选择不重新校验时,应限定范围并说明理由
  • 仅当至少有一个 promise 会以流式方式传输时才使用 defer()(传入前不要 await)
  • 每个 <Await> 都由 <Suspense> 包裹,并且设有 errorElement
  • useRevalidator().revalidate() 仅用于焦点、轮询或 SSE 场景——不要在 <Form> 提交或 fetcher.submit 后立即调用(Remix 已会重新校验)。

有效模式(不要标记)

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

  • 用于客户端专属数据的 useEffect — Loader 在服务端运行;localStorage、window 尺寸、IntersectionObserver 和仅限浏览器的 API 应放在 useEffect 中。
  • loader 返回 null — Loader 可以合理地返回 null(例如,可选资源不存在);只有在应该 throw 一个 404 时才标记。
  • 将 useLoaderData<typeof loader>() 用作类型注解 — <typeof loader> 是传入 SerializeFrom<T> 的泛型参数,并非 as 风格的类型断言。不要将其标记为“不安全的类型转换”。
  • 直接返回 new Response(body, init) — v2 路由可以返回任意 Response;json() 是一种便捷封装,并非必需。非 JSON 响应体(二进制、文本、流)正确地不使用 json()。
  • 在 action 中使用 return redirect(...) — 在 action 中,return redirect(...) 和 throw redirect(...) 都合法;只有在非 action 辅助函数中需要退出调用函数时,才必须使用 throw。
  • 声明 loader 时不带 request 参数 — Loader 可以只解构所需内容({ params }、{ context },或不带参数的 ());未使用的参数不是错误。
  • 无关 action 之后重新验证父级 loader — 这是 Remix 的默认行为,并非代码异味。只有在存在 shouldRevalidate 且其行为错误时才标记。
  • Action 成功时返回 json({ errors }, { status: 400 }) — 这是规范的验证错误模式(会让表单路由保持渲染,并显示字段错误)。这与“成功时不重定向”的反模式不同。
  • 将 useRevalidator 用于焦点变化、轮询或跨标签页同步 — 这些都是文档中说明的用例;只有在 <Form> 提交或 fetcher.submit 后立即手动调用 revalidate(),而 Remix 本身已会重新验证时,才标记。
  • SerializeFrom 引起的类型变化 — 反序列化后,Date 被标记为 string、Map 被标记为 {},这是正确的线格式行为,并非类型错误。

视具体情境而定的规则

仅在符合特定情境时标记以下问题:

问题仅当以下条件成立时才标记
缺少 loader(改用 useEffect)数据可在服务端获取,且不是读取仅限浏览器的 API
loader 返回原始 ORM 对象对象包含审查者不会贴到截图中的字段(密码、令牌、内部标记)
Action 成功时返回 json该 action 通过 <Form> 调用并导致 URL 变化——而非通过 useFetcher 调用
缺少待处理状态 UI文件其他位置没有用于驱动同一界面的 nav.state / fetcher.state 引用
shouldRevalidate 返回 false函数体没有条件,或从未引用 formAction / currentParams / nextParams
手动调用 useRevalidator().revalidate()该调用紧跟 Remix 管理的变更操作(<Form> 提交、fetcher.submit)——而非焦点变化、轮询或 websocket
使用了 defer()defer({...}) 载荷中的每个 Promise 在调用前都已使用 await

硬性门槛(编写问题发现前)

按顺序执行这些检查。在本批次所有门槛均通过之前,请勿起草面向用户的问题发现。

  1. 位置证据 — 通过: 每个问题都列出路由模块的仓库路径,以及你读取的文件中的行范围或简短原文引用(不能凭记忆或仅根据差异猜测)。如果没有指向 export async function loader|action 的路径,则不得报告 Loader/Action 问题。

  2. 豁免检查 — 通过: 对于每个问题,你都能用一行说明它为何不属于 有效模式(不要标记) 的范围。特别要确认:useEffect 并非用于加载仅限客户端的数据;裸 Response 返回值并非有意采用非 JSON 格式;返回 null 的 loader 并非合法的可选读取。

  3. 类型注解与类型断言检查 — 通过: 在标记 Loader/Action 数据消费中的“非安全类型转换”之前,确认代码使用的是 as(断言),而非 useLoaderData<typeof loader>()(注解)或 useActionData<typeof action>()(注解)。泛型形式是文档中规定的安全方式,不得标记。

  4. v1 遗留项检查 — 通过: 在标记“缺少待处理状态”之前,先在文件中搜索 useTransition、transition.submission、fetcher.type、formMethod === "post" 或 formMethod==='post'(小写,空格或引号形式不限),以及 LoaderArgs / ActionArgs。如果存在,这属于 v1 遗留迁移问题,而非缺少功能的问题——请据此标注。

  5. 协议 — 通过: 已针对本次审查完成 review-verification-protocol 中的审查前验证清单。

何时加载参考资料

审查问题

  1. 首次渲染所需的数据是否在 loader 中获取,还是被放在会破坏 SSR 和重新验证的 useEffect 中?
  2. 每个 loader 是否都返回经过筛选的 DTO,还是原始 ORM 记录(包含 password、token、internal_* 字段)泄漏到了浏览器?
  3. 每个 action 是否都在访问数据库前,使用 schema 验证 request.formData()?
  4. 每个 action 的成功分支是否都使用 redirect(...),以确保刷新/返回行为正确(PRG)?
  5. 消费端代码是否使用 useLoaderData<typeof loader>()(注解),而非 useLoaderData() as Foo(断言)?
  6. 是否仍有 v1 遗留项(useTransition、transition.submission、fetcher.type、小写的 formMethod、LoaderArgs / ActionArgs)?
  7. shouldRevalidate 是否返回字面量 false,或者使用 defaultShouldRevalidate 并仅针对特定情况选择退出?
  8. 是否仅在传入至少一个尚未解析的 Promise 时才使用 defer(),并且每个 <Await> 是否都包裹在带有 errorElement 的 <Suspense> 中?

补充文档

提交问题发现前

完成硬性门槛(尤其是第 5 条),然后仅报告仍通过 review-verification-protocol 审查前检查的问题。

评论

支持 Markdown