launch-quickly
Decisions

0011 — State management has a single answer

Server state is RSC and server actions. URL state is nuqs. Local state is useState. Everything else is a lint error.

Context

State management is where a codebase accumulates archaeology. One feature uses Redux because it was written in 2022, another uses SWR because someone liked it, a third uses Context holding server data. All three work. Together they mean nobody can answer "where does this value live?" without reading the file.

Decision

One answer per kind of state, enforced by no-restricted-imports:

  • Server state — RSC and server actions
  • URL statenuqs
  • Local stateuseState
  • Client-global non-server state — Zustand, and only in src/features/<name>/store.ts

Redux, MobX, Jotai, Recoil, SWR and Context-holding-server-data are banned. Each ban carries its own message; SWR's is the most substantive: "Read in a Server Component; use TanStack Query in a feature hook if you truly need client fetching."

What is honestly argued, and what is not

The principle is single answer beats best answer. A codebase where every developer picks their favourite is worse than one that picks adequately and picks once.

There is no argument anywhere that Jotai is worse than Zustand, and this page is not going to invent one. The rejections are a consequence of the single-answer rule, not a verdict on each library.

Zustand is confined rather than banned, which is the one place the rule bends — a feature store is a real need and the boundary keeps it from becoming an alternative to server state.

The nuqs incident

The positive case for nuqs in a list view is documented as three non-negotiable options — shallow: false, startTransition, clearOnDefault — each with a one-line reason.

The sharper story is what its absence did. A missing nuqs adapter reached production: the page threw before painting, and the entire e2e suite stayed green because it only ever asserted that signed-out visitors were redirected to /sign-in. No test rendered an authenticated page at all.

That is why the signed-in Playwright fixture exists, and why the rule is render real pages, do not assert redirects. The state library is almost incidental to that lesson; the testing gap it exposed was the expensive part.

On this page