Why
The admin dashboard shipped broken (#231 hotfix): a client component value-imported a lib module that imported @/lib/db at module scope, putting the Neon client in the browser bundle where neon() throws at hydration. None of the CI gates (eslint, tsc, vitest) can catch client-graph leaks — the bug only manifested at runtime in the browser.
What
Add a build-time guard so this class of bug fails next build (and therefore the Vercel deploy) instead of shipping:
- Add the
server-only package and import "server-only"; at the top of src/lib/db/index.ts (or wherever db is constructed). Next.js then hard-errors any build where the module is reachable from a client component.
- Vitest compatibility: tests import db-adjacent modules (usually mocked, but resolution still happens). Alias
server-only to an empty module in vitest.config.ts (e.g. resolve.alias: { "server-only": <stub path> }) so unit tests are unaffected.
- Verify: (a) all three checks pass; (b)
pnpm exec next build passes on clean main; (c) temporarily add a value import of @/lib/db to a "use client" component and confirm next build FAILS with the server-only error (then revert the temp change — do not commit it; describe the experiment in the PR body).
Constraints
- Dep add → pnpm-lock.yaml changes; if another dep-adding PR is in flight, coordinate per CLAUDE.md (merge one, regenerate lock).
- All three checks pass before PR.
Why
The admin dashboard shipped broken (#231 hotfix): a client component value-imported a lib module that imported
@/lib/dbat module scope, putting the Neon client in the browser bundle whereneon()throws at hydration. None of the CI gates (eslint, tsc, vitest) can catch client-graph leaks — the bug only manifested at runtime in the browser.What
Add a build-time guard so this class of bug fails
next build(and therefore the Vercel deploy) instead of shipping:server-onlypackage andimport "server-only";at the top ofsrc/lib/db/index.ts(or whereverdbis constructed). Next.js then hard-errors any build where the module is reachable from a client component.server-onlyto an empty module invitest.config.ts(e.g.resolve.alias: { "server-only": <stub path> }) so unit tests are unaffected.pnpm exec next buildpasses on clean main; (c) temporarily add a value import of@/lib/dbto a "use client" component and confirmnext buildFAILS with the server-only error (then revert the temp change — do not commit it; describe the experiment in the PR body).Constraints