remix-v2-perf-ssr-review — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited remix-v2-perf-ssr-review (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Targets TypeScript route modules importing from @remix-run/*. See remix-v2-perf-ssr for canonical patterns.
| Issue Type | Reference |
|---|---|
Missing headers export, unsafe public cache, child-drops-parent headers, missing Vary: Cookie, Set-Cookie + public | references/caching-headers.md |
Server libs imported without .server.ts, process.env.SECRET_* leaks, typeof window substituted for .server.ts | references/server-client-split.md |
new Date() in render, Math.random() in keys, locale formatting without explicit locale, missing useId(), blanket suppressHydrationWarning | references/hydration.md |
prefetch="render" on every link, defer for fast data, missing <Suspense> around <Await>, prefetch to side-effect routes | references/prefetch-streaming.md |
dangerouslySetInnerHTML with untrusted data, missing loading="lazy", missing links preload, stylesheet injected in body | references/assets.md |
headers (even if the answer is no-store)headers (otherwise they silently inherit the parent's policy)Cache-Control: public is never set on auth'd or cookie-bearing responsesVary: Cookie is set when cache decision depends on sessionprisma, bcrypt, node:fs, jsonwebtoken) live in *.server.ts or app/.server/process.env.STRIPE_SECRET_KEY, etc.) is read only inside loaders/actions or .server moduleswindow.ENV, never raw process.envtypeof window === "undefined" is not used as a substitute for .server.ts (treeshaking is unreliable)new Date(), Math.random(), Date.now(), crypto.randomUUID() in JSX render pathtoLocaleDateString, Intl.DateTimeFormat) passes an explicit localeuseId(), not Math.random() or counterssuppressHydrationWarning is scoped to a single element with a code comment explaining why<Link prefetch="render"> is reserved for above-the-fold critical nav, not lists<PrefetchPageLinks> does not target routes whose loaders have side effects (analytics, mutations)<Await> is wrapped in <Suspense> and has an errorElementdefer() is used only for genuinely slow data (>~50ms); fast data is awaitedloading="lazy" and have width/heightlinks export, not injected in bodyThese are correct Remix v2 usage and must not be reported as issues:
no-store may legitimately omit headers. Flag only if the route serves cacheable public content with no headers.Date.now(), Math.random(), crypto.randomUUID() inside effects.window belong in *.client.ts by convention; do not flag the file extension. ${id}-input ` is the documented pattern for multi-element components; do not flag as "non-stable id."Apply these only when the specific context applies:
| Issue | Flag ONLY IF |
|---|---|
Missing headers export | Route serves cacheable public content (not auth'd, not personalized, not intentionally no-store) |
Child route missing headers | An ancestor exports headers AND its policy is broader than the child's cacheability (e.g., parent caches public + s-maxage, child serves personalized data) |
Cache-Control: public | Loader actually reads session / user state (or response carries Set-Cookie) |
Vary: Cookie missing | Loader branches response shape on a cookie (theme, locale, session) AND the cache is public/s-maxage |
new Date() / Math.random() / Date.now() | Call site is in render path — NOT in useEffect, event handler, <ClientOnly>, or post-hydration code |
| Locale formatting without locale | Result is rendered into JSX (not used only inside an effect / handler) |
<Link prefetch="render"> | Link is inside a list / .map() iterator (not above-the-fold critical nav) |
<Link prefetch="intent"> to side-effect loader | Loader has observable side effects (analytics write, counter increment, log emit) AND doesn't branch on the Purpose: prefetch header |
Server lib import without .server.ts | Importing file is reachable from the client graph (route module, non-.server util reached from a component) |
process.env.SECRET_* reference | Reference is in a component body or in a non-.server module reached from the client graph |
Missing loading="lazy" on image | Image is rendered below the fold (not in <header>, hero section, or above any <main> content) |
Missing width/height on image | Project does NOT use a build-time image processor that injects dimensions |
Run these in order. Do not draft user-facing findings until every gate passes for the batch you are about to report.
.server claims without a concrete file path are not reportable.headers export is not on an intentionally-uncacheable route, confirm .client.ts is not a legitimate client-only library, confirm suppressHydrationWarning is not scoped + commented.new Date(), Math.random(), Date.now(), crypto.randomUUID(), or locale formatting, confirm the call site is in the render path of a component. Calls inside useEffect, useLayoutEffect, event handlers, callbacks passed to setTimeout/requestAnimationFrame, or inside <ClientOnly>{() => ...}</ClientOnly> are post-hydration and must not be flagged.headers on a child" as silent cache inheritance, confirm an ancestor route in the matched chain actually exports headers (search the route file tree for export const headers or export function headers) AND that the inherited policy is wider than the child's cacheability profile. If no ancestor exports headers, the issue is just "no caching configured," not "child silently inherits parent's cache.".server utility transitively imported by a route's default export, or a .client.ts file. Imports inside loader, action, headers, or other .server.ts modules are not leaks.headers exports, Cache-Control strings, CDN policy → references/caching-headers.md.server.ts / .client.ts files, imports of prisma/bcrypt/fs, or process.env access → references/server-client-split.md<Link prefetch>, <PrefetchPageLinks>, defer, <Await>, <Suspense>, or <RemixServer abortDelay> → references/prefetch-streaming.md<img>, <link>, dangerouslySetInnerHTML, font/CSS loading, or links export → references/assets.mdCache-Control policy, even if "no cache"?public caching, with Vary: Cookie where session influences the response?prisma, bcrypt, fs, secret env access) live in *.server.ts modules that the build will reject if leaked?window.ENV rather than spread from process.env?new Date() / Math.random() / locale formatting calls limited to effects, handlers, or <ClientOnly> — not render?useId()?<Link prefetch> modes matched to context (render only above the fold, intent for nav, viewport/intent in lists)?defer() used only for genuinely slow data, with <Await> always paired with <Suspense> AND errorElement?links export?dangerouslySetInnerHTML used only with sanitized HTML or safely serialized JS?Complete Hard gates (especially gate 3 — hydration-context check, and gate 5 — server/client boundary check), then report only issues that still pass the review-verification-protocol pre-report checks. Finding format: [FILE:LINE] ISSUE_TITLE with a verbatim quote of the offending code and a one-line rationale tied to the specific Remix v2 contract being violated.
headers exports, CDN cache policy, Vary, parent/child merge, Set-Cookie interactions → references/caching-headers.md.server.ts / .client.ts boundaries, process.env access, window.ENV pattern → references/server-client-split.mdDate/Math.random/locale issues, useId, suppressHydrationWarning scope → references/hydration.md<Link prefetch> modes, <PrefetchPageLinks> targets, defer/<Await>/<Suspense> structure → references/prefetch-streaming.mddangerouslySetInnerHTML, image loading/width/height, font/CSS preload, stylesheet placement → references/assets.md~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.