关于 remix-v2-perf-ssr-review
面向审查 Remix v2 TypeScript 路由模块的性能与 SSR 技能。它提供缓存、服务端与客户端边界、水合、预取和资源加载的检查清单,并要求基于代码位置与上下文验证问题。
主要功能
- 01检查缓存标头与 Cookie 变化
- 02审查服务端和客户端模块边界
- 03识别 SSR 水合风险
- 04检查预取与流式数据用法
- 05审查图片、字体和样式资源
适用场景
- 01审查路由缓存策略
- 02排查服务端代码泄漏风险
- 03定位渲染导致的水合差异
- 04检查预取和资源加载配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ existential-birds/ beagle --skill remix-v2-perf-ssr-review安装后在 Claude Code 中输入/remix-v2-perf-ssr-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/assets.mdcaching-headers.mdhydration.mdprefetch-streaming.mdserver-client-split.md详细介绍
Remix v2 性能 / SSR 代码审查
目标是导入 @remix-run/* 的 TypeScript 路由模块。规范模式请参阅 remix-v2-perf-ssr。
快速参考
| 问题类型 | 参考 |
|---|---|
缺少 headers 导出、不安全的 public 缓存、子路由丢弃父路由的标头、缺少 Vary: Cookie、Set-Cookie 与 public 并用 | references/caching-headers.md |
导入的服务端库没有放在 .server.ts 文件中、泄露 process.env.SECRET_*、用 typeof window 代替 .server.ts | references/server-client-split.md |
渲染时调用 new Date()、在键中使用 Math.random()、未显式指定区域设置的本地化格式化、缺少 useId()、无差别使用 suppressHydrationWarning | references/hydration.md |
每个链接都设置 prefetch="render"、对快速数据使用 defer、<Await> 外缺少 <Suspense>、预取有副作用的路由 | references/prefetch-streaming.md |
dangerouslySetInnerHTML 使用不受信任的数据、缺少 loading="lazy"、缺少 links 预加载、在 body 中注入样式表 | references/assets.md |
审查清单
- 提供数据的路由会导出
headers(即使响应为no-store) - 提供个性化数据的子路由会导出自己的
headers(否则会悄无声息地继承父路由的策略) - 绝不对需要身份验证或带有 cookie 的响应设置
Cache-Control: public - 缓存决策取决于会话时,会设置
Vary: Cookie - 仅限服务端使用的库(
prisma、bcrypt、node:fs、jsonwebtoken)位于*.server.ts或app/.server/中 - 仅在 loaders/actions 或
.server模块中读取机密环境变量(如process.env.STRIPE_SECRET_KEY) - 暴露给客户端的环境变量会列入白名单并放入
window.ENV,绝不直接暴露process.env - 不会使用
typeof window === "undefined"代替.server.ts(摇树优化并不可靠) - JSX 渲染路径中不会调用
new Date()、Math.random()、Date.now()或crypto.randomUUID() - 本地化格式化(
toLocaleDateString、Intl.DateTimeFormat)会传入明确的区域设置 - 生成 ID 的组件会使用
useId(),而非Math.random()或计数器 -
suppressHydrationWarning仅用于单个元素,并附有说明原因的代码注释 -
<Link prefetch="render">仅用于首屏关键导航,而非列表 -
<PrefetchPageLinks>不会指向 loader 有副作用(分析、变更)的路由 - 每个
<Await>都由<Suspense>包裹,并设置errorElement - 仅对确实较慢的数据(>~50ms)使用
defer();快速数据会等待完成 - 首屏以下的图片使用
loading="lazy",并设置width/height - 关键字体 / CSS 通过
links导出预加载,而非在 body 中注入
有效模式(不要标记)
以下是正确的 Remix v2 用法,不得报告为问题:
- 有意关闭缓存的路由未导出
headers— 经过身份验证的仪表板、账户页面,以及由已返回no-store的布局包裹的路由,可以合理地省略headers。只有当路由提供可缓存的公开内容但没有headers时才标记。 useEffect中的new Date()— 仅在客户端水合后运行;不会导致 SSR 不匹配。效果函数中的Date.now()、Math.random()、crypto.randomUUID()也一样。- 事件处理器中的
Math.random()/new Date()— 处理器会在水合后运行。只有当该值在渲染期间使用时才标记。 - 在单个
<time>(或类似)元素上使用suppressHydrationWarning,并附有清晰注释 — 对于已知会发生差异的值(如在客户端格式化的绝对时间戳),这是可接受的有限例外。只有当它应用于包裹大型子树的父元素,或没有说明时才标记。 - 客户端专用库的
.client.ts文件 — Stripe.js、地图小部件、读取window的图表库按惯例应放在*.client.ts中;不要因文件扩展名而标记。 useId()后追加额外字符 —`${id}-input`是多元素组件的文档化模式;不要将其标记为“非稳定 ID”。- SSR 中渲染原始 ISO 字符串,再在
useEffect中重新格式化 — 这是规范的水合安全时间模式;只有在渲染期间重新格式化时才标记。 - 返回
{}或no-store的headers导出 — 明确表示“不要缓存”是有意为之,不应标记为误用。 - 标准导航中的
<Link prefetch="intent">— 这是推荐的默认设置;只有当 loader 有副作用时才标记。 - 通过
headers导出将loaderHeaders转发到文档 — 将数据策略与文档策略放在一起是文档化模式,不是重复设置。
依上下文适用的规则
仅在符合特定上下文时应用:
| 问题 | 仅当以下情况时标记 |
|---|---|
未导出 headers | 路由提供可缓存的公开内容(未经过身份验证、非个性化,且并非有意设置为 no-store) |
子路由未导出 headers | 某个祖先导出了 headers,且其策略范围比子路由的可缓存性更广(例如,父路由缓存公开内容并使用 s-maxage,而子路由提供个性化数据) |
Cache-Control: public | Loader 实际读取了 session / 用户状态(或响应包含 Set-Cookie) |
缺少 Vary: Cookie | Loader 根据 cookie(主题、区域设置、session)改变响应结构,且缓存设置为 public/s-maxage |
new Date() / Math.random() / Date.now() | 调用位置在渲染路径中——不在 useEffect、事件处理器、<ClientOnly> 或水合后的代码中 |
| 未指定区域设置的区域格式化 | 结果被渲染到 JSX 中(而非仅在效果函数 / 处理器中使用) |
<Link prefetch="render"> | 链接位于列表 / .map() 迭代器中(而不是首屏关键导航中) |
<Link prefetch="intent"> 指向有副作用的 loader | Loader 有可观察的副作用(写入分析数据、递增计数器、输出日志),且没有根据 Purpose: prefetch 标头进行分支处理 |
未使用 .server.ts 的服务端库导入 | 导入文件可从客户端依赖图访问(路由模块、从组件可访问的非 .server 工具模块) |
process.env.SECRET_* 引用 | 引用位于组件主体中,或位于客户端依赖图可访问的非 .server 模块中 |
图片缺少 loading="lazy" | 图片在首屏以下渲染(不在 <header>、主视觉区域或任何 <main> 内容之前) |
图片缺少 width/height | 项目未使用会在构建时注入尺寸的图片处理器 |
硬性门槛(撰写问题报告前)
按顺序执行以下检查。在本批次的所有门槛都通过之前,不要撰写面向用户的问题报告。
-
位置证据 — 通过条件: 每个问题都列出仓库路径,以及你实际读取的文件中的行范围或简短原文引用(不能凭记忆或只根据差异猜测)。没有具体文件路径的缓存、hydration 和
.server相关问题不得报告。 -
豁免检查 — 通过条件: 对于每个问题,你都能用一行说明它为何不属于 有效模式(不要标记)所涵盖的情况。尤其要确认:缺少
headers导出的问题不在有意禁用缓存的路由上;.client.ts不是合法的仅限客户端库;suppressHydrationWarning不是在特定范围内使用且附有注释。 -
Hydration 上下文检查 — 通过条件: 在标记
new Date()、Math.random()、Date.now()、crypto.randomUUID()或区域格式化之前,确认调用点位于组件的渲染路径中。在useEffect、useLayoutEffect、事件处理程序、传给setTimeout/requestAnimationFrame的回调中,或<ClientOnly>{() => ...}</ClientOnly>内的调用都发生在 hydration 后,不得标记。 -
父子 headers 链检查 — 通过条件: 在将“子级缺少
headers,导致静默继承缓存策略”标记为问题之前,确认匹配链中的某个祖先路由确实导出了headers(搜索路由文件树中的export const headers或export function headers),并且继承的策略比子级的可缓存性范围更广。如果没有祖先导出 headers,这个问题只是“未配置缓存”,而不是“子级静默继承父级缓存”。 -
服务器/客户端边界检查 — 通过条件: 在将服务器库导入标记为泄漏之前,确认导入文件可从客户端依赖图访问——也就是说,它是路由模块、由路由默认导出传递导入的非
.server工具,或.client.ts文件。loader、action、headers或其他.server.ts模块中的导入不属于泄漏。 -
流程 — 通过条件: 已完成本次审查的 review-verification-protocol 中的预报告验证清单。
何时加载参考资料
- 审查路由的
headers导出、Cache-Control字符串、CDN 策略时 → references/caching-headers.md - 审查
.server.ts/.client.ts文件、prisma/bcrypt/fs的导入,或process.env的访问时 → references/server-client-split.md - 审查渲染日期、ID、区域格式化值或浏览器全局对象的组件时 → references/hydration.md
- 审查
<Link prefetch>、<PrefetchPageLinks>、defer、<Await>、<Suspense>或<RemixServer abortDelay>时 → references/prefetch-streaming.md - 审查
<img>、<link>、dangerouslySetInnerHTML、字体/CSS 加载或links导出时 → references/assets.md
审查问题
- 每个提供可缓存数据的路由是否都声明了
Cache-Control策略,即使是“no cache”也不例外? - 个性化路由是否都未使用
public缓存,并在会话会影响响应时设置Vary: Cookie? - 服务端库(
prisma、bcrypt、fs、机密环境变量访问)是否都位于*.server.ts模块中,以便构建过程能在其泄漏时拒绝构建? - 公共环境变量是否通过白名单加入
window.ENV,而不是从process.env展开? new Date()/Math.random()/ 区域格式化调用是否仅限于副作用、处理程序或<ClientOnly>中,而不在渲染期间调用?- 需要 ID 的组件是否使用
useId()? <Link prefetch>模式是否与使用场景匹配(仅在首屏上方使用render,导航使用intent,列表中使用viewport/intent)?defer()是否仅用于真正耗时的数据,并且<Await>是否始终与<Suspense>和errorElement配合使用?- 图片是否设置了尺寸、在首屏下方延迟加载,关键字体/CSS 是否通过
links导出预加载? dangerouslySetInnerHTML是否仅用于经过清理的 HTML 或安全序列化的 JS?
提交发现前
完成 硬性门槛(尤其是门槛 3——水合上下文检查,以及门槛 5——服务端/客户端边界检查),然后仅报告仍通过 review-verification-protocol 报告前检查的问题。发现项格式:[FILE:LINE] ISSUE_TITLE,附上违规代码的逐字引用,并用一句话说明其违反的具体 Remix v2 契约。
补充文档
- 审查
headers导出、CDN 缓存策略、Vary、父子合并、Set-Cookie交互 → references/caching-headers.md - 审查
.server.ts/.client.ts边界、process.env访问、window.ENV模式 → references/server-client-split.md - 审查渲染时的
Date/Math.random/区域格式化问题、useId、suppressHydrationWarning的适用范围 → references/hydration.md - 审查
<Link prefetch>模式、<PrefetchPageLinks>目标、defer/<Await>/<Suspense>结构 → references/prefetch-streaming.md - 审查
dangerouslySetInnerHTML、图片的loading/width/height、字体/CSS 预加载、样式表位置 → references/assets.md - 规范模式和决策门槛 → remix-v2-perf-ssr