fix(ssr): harden credential query-key factory + fetchers against the 'use client' stub bug#5206
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryLow Risk Overview Refactor: Moves Guardrails: Adds Reviewed by Cursor Bugbot for commit f2ccd0b. Configure here. |
Greptile SummaryThis PR hardens the SSR boundary by extracting credential query-key factories and standalone fetchers out of
Confidence Score: 5/5Safe to merge — purely a structural refactor with no behavior changes and full consumer coverage confirmed by tsc and the new CI guard. The change extracts three well-scoped primitives into standalone non-client modules and updates every known consumer. The new utility files are exact replicas of what was removed, the 'use client' hook files re-import them so their own APIs are unchanged, and the CI guard closes the regression vector going forward. No logic is altered. No files require special attention. The CI guard script covers direct server-surface imports; transitive paths through library files remain outside its scope by design, consistent with the project's existing boundary checks. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
subgraph Server["Server-evaluated (no 'use client')"]
A["blocks/blocks/credential.ts"]
B["lib/workflows/comparison/resolve-values.ts"]
end
subgraph Utils["hooks/queries/utils/ (no 'use client')"]
C["credential-keys.ts\n(workspaceCredentialKeys)"]
D["fetch-workspace-credentials.ts\n(fetchWorkspaceCredentialList)"]
E["fetch-credential-set.ts\n(fetchCredentialSetById)"]
end
subgraph Client["'use client' hooks"]
F["hooks/queries/credentials.ts"]
G["hooks/queries/credential-sets.ts"]
end
subgraph Consumers["Other client consumers"]
H["invitations.ts"]
I["organization.ts"]
J["secrets-manager.tsx"]
end
A -->|now imports| C
A -->|now imports| D
B -->|now imports| E
F -->|re-imports| C
F -->|re-imports| D
G -->|re-imports| E
H -->|now imports| C
I -->|now imports| C
J -->|now imports| C
style Server fill:#ffe0e0
style Utils fill:#e0ffe0
style Client fill:#e0e8ff
style Consumers fill:#fff8e0
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
subgraph Server["Server-evaluated (no 'use client')"]
A["blocks/blocks/credential.ts"]
B["lib/workflows/comparison/resolve-values.ts"]
end
subgraph Utils["hooks/queries/utils/ (no 'use client')"]
C["credential-keys.ts\n(workspaceCredentialKeys)"]
D["fetch-workspace-credentials.ts\n(fetchWorkspaceCredentialList)"]
E["fetch-credential-set.ts\n(fetchCredentialSetById)"]
end
subgraph Client["'use client' hooks"]
F["hooks/queries/credentials.ts"]
G["hooks/queries/credential-sets.ts"]
end
subgraph Consumers["Other client consumers"]
H["invitations.ts"]
I["organization.ts"]
J["secrets-manager.tsx"]
end
A -->|now imports| C
A -->|now imports| D
B -->|now imports| E
F -->|re-imports| C
F -->|re-imports| D
G -->|re-imports| E
H -->|now imports| C
I -->|now imports| C
J -->|now imports| C
style Server fill:#ffe0e0
style Utils fill:#e0ffe0
style Client fill:#e0e8ff
style Consumers fill:#fff8e0
Reviews (3): Last reviewed commit: "docs+ci: codify the 'use client' server-..." | Re-trigger Greptile |
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit aaaf238. Configure here.
…modules
Preventively closes the same 'use client' SSR client-reference-stub class that
crashed the tables page. Server-evaluated modules (the credential block def, the
workflow-comparison helpers) imported workspaceCredentialKeys /
fetchWorkspaceCredentialList / fetchCredentialSetById from 'use client' hook
modules, where they resolve to client-reference stubs on the server (a future
server call path would throw 'X is not a function').
Extract them into non-client hooks/queries/utils/{credential-keys,
fetch-workspace-credentials,fetch-credential-set}.ts (mirroring folder-keys.ts /
fetch-workflow-envelope.ts) and import from there. No behavior change — these
values were only ever called from browser paths.
…t-boundary Document the Next.js rule that server code can only render a 'use client' export as a component, never call it (server imports resolve to client-reference stubs that throw — the tables-page crash). Add the rule to .claude/rules/sim-queries.md + a cross-ref in sim-architecture.md. Add scripts/check-client-boundary-imports.ts (wired into CI as check:client-boundary) that flags any value import from a 'use client' module in a server-evaluated, non-JSX surface (prefetch / route handler / trigger / block definition), so this class can't silently recur. Escape hatch: // client-boundary-allow: <reason>.
aaaf238 to
f2ccd0b
Compare
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f2ccd0b. Configure here.
Summary
Preventive hardening for the same bug class that crashed the tables page (#5204): a server-evaluated module importing a runtime value from a
'use client'module resolves to a client-reference stub on the server, so calling it throws (X is not a function). A comprehensive audit found no other live crashes, but two latent fragilities of this class — fixed here so they can't fire:blocks/blocks/credential.ts(block defs are evaluated server-side) importedworkspaceCredentialKeys+fetchWorkspaceCredentialListfrom the'use client'hooks/queries/credentials.ts.lib/workflows/comparison/resolve-values.ts(reached server-side via the comparison barrel) importedfetchCredentialSetByIdfrom the'use client'hooks/queries/credential-sets.ts.Both are only called from browser paths today, so neither crashes — but the top-level imports are stubs on the server, waiting to fire.
Fix
Extract the affected primitives into non-
'use client'modules, mirroring the existingfolder-keys.ts/fetch-workflow-envelope.tsprecedents, and import from there everywhere:hooks/queries/utils/credential-keys.ts—workspaceCredentialKeyshooks/queries/utils/fetch-workspace-credentials.ts—fetchWorkspaceCredentialListhooks/queries/utils/fetch-credential-set.ts—fetchCredentialSetByIdcredentials.ts/credential-sets.tsnow import these back (used by their hooks); all consumers (block, invitations, organization, resolve-values, + the test mock) point at the new modules. No behavior change.Type of Change
Testing
tsc0 errors (confirms every importer was updated), Biome clean,check:react-querypasses.next build(the gate that actually exercises the SSR boundary).Checklist