InfinitiBit Design System

RSC Smoke Gate

A real Next.js App Router build that fails when a component isn't safe across the server/client boundary.

RSC Smoke Gate

@infinitibit_gmbh/ui is built to be consumed in React Server Components — Next.js App Router is the canonical RSC consumer. axe, keyboard and visual all check rendered behavior; none check the server/client module boundary. A component that uses a hook or browser API without a "use client" directive compiles fine under Vite/Storybook, passes type-check, and still breaks a consumer's next build. The RSC gate is the only one that catches that.

It builds a minimal Next.js App Router app (tests/rsc-smoke) whose server page crosses the boundary — it imports the real Icon from the built package and renders a "use client" island — and runs next build. App Router prerenders the page, so an RSC-unsafe import fails the build. It runs inside pnpm gate, so it fires locally and in CI with no extra steps.

Use it

There's nothing to author per component — the gate is a build. To run it:

pnpm gate          # full suite, incl. RSC smoke
pnpm verify:rsc    # RSC gate only

verify:rsc builds @infinitibit_gmbh/ui (so Next imports the published dist, not source) then runs next build on tests/rsc-smoke. Green means the package is server/client-boundary safe. If a component reaches the server graph unsafely, the build fails and Next names the file and the offending hook — for example:

You're importing a component that needs `useState`. This React Hook only works in a
Client Component. To fix, mark the file (or its parent) with the "use client" directive.
  ./app/page.tsx

Fix the component (add "use client" where it belongs, or stop importing a client-only module into a server component) until the gate is green.

How it works

  • A real next build, not a simulation. tests/rsc-smoke/app/page.tsx is a server component that imports Icon from @infinitibit_gmbh/ui/icons and renders a local "use client" island (app/counter.tsx). next build compiles the RSC graph and statically prerenders the page — the only honest proof of RSC-safety, because the server/client split is a property of the RSC compiler, not of types or jsdom.
  • It tests the published artifact. Next resolves the import export condition, so it imports @infinitibit_gmbh/ui's dist — what consumers actually get. verify:rsc builds the library first.
  • Wired by inheritance. verify:rsc joins the test script's verify:* chain, so pnpm testpnpm gateci.yml pick it up with no workflow edit — the extension model from Testing & CI. Unlike visual, it needs no pinned container: a build resolves the boundary or it doesn't, identically everywhere.
  • A fixture, kept out of turbo. tests/rsc-smoke is a workspace member (so Next deps install) but defines no turbo task, so turbo run * skips it and Next stays out of the bare gate's turbo runs. The fixture disables ESLint during its build (App Router requires default exports, which the library lint rules ban); type-checking stays on, and the RSC error is a compile error regardless.

See ADR 0013 for the full rationale (and why it builds the real package instead of linting for directives).

Maintain it

  • Extend coverage by importing a new component into tests/rsc-smoke/app/page.tsx (a server component). From the golden Button (#4), swap or join Icon there — that's the whole wiring.
  • Keep the page a server component. Its lack of "use client" is the point: it's what makes an unsafe import fail. Put new client-only demos behind a "use client" island like app/counter.tsx.
  • Pin moves deliberately. Next/React versions live in tests/rsc-smoke/package.json; bump them on purpose, then re-run pnpm verify:rsc.
  • Build, not behavior. This gate proves the package builds in RSC; it doesn't assert runtime output. Pair it with the axe, keyboard and visual gates for rendered behavior.

On this page