react-brain
Browse decisions
React & language foundations 3App architecture 11UI 13Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness benchnewsletterarchitecturechangelogmethodologyroadmap

entriestooling-ops · verified 2026-09-24 · react + react-native

Developer experience — CI, lint/format, hooks, monorepo, onboarding

reviewedconfidence: highthis tier holds 83% on the public scorecard (6/11 graded · 0 overturned) →

related decisions: react-core · build · security · ai-devtools

cited by: typescript · component-libs · build · ai-devtools

re-verified 9× — 2026-09-24 · 2026-09-21 · 2026-09-10 · 2026-08-24 · 2026-08-18 · 2026-07-28 +3 · changelog

recommendation

Gate tests + typecheck + lint on every PR in CI, and enforce the same locally with a pre-commit hook. For lint/format, default to Biome (one fast tool) unless you need ESLint's react-hooks rules, in which case ESLint flat config + Prettier.

  • has tests but no CI → CI (GitHub Actions or similar; add it FIRST — ungated invariants silently rot, a real finding in audited production apps)
  • needs ESLint react-hooks rules → ESLint flat config + Prettier (react-hooks is the remaining ESLint-only surface; React Compiler rules are native in Oxlint 1.79+ and in Biome 2.5.8+ nursery)
  • kept typescript-eslint ONLY for type-aware rules → Oxlint + oxlint-tsgolint (`oxlint --type-aware`, stable, 59/61 rules; ESLint plugins can run through Oxlint's jsPlugins)
  • no git hook yet → husky + lint-staged (run lint/format on staged files; lefthook covers the same job if already installed)
  • multi-package repo → pnpm workspaces + Turborepo (cached task graph; Nx if the repo already runs it; pnpm 11.11+ versions and releases the workspace, so no separate changesets tool)
  • normative styleguide but no linter config → lint config (encode the styleguide rules so they are enforced mechanically, not by review)
  • lots of agent-written React landing in the repo → react-doctor (a CI or agent-skill pass on top of lint)

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
CI (GitHub Actions / similar)Gate tests + typecheck + lint + build on every PR — the single highest-leverage DX baseline, because an invariant that is not gated silently rots.
lint/format — Biome or ESLint (flat) + PrettierBiome is one fast Rust tool for lint + format; ESLint flat + Prettier has the bigger plugin ecosystem and the only react-hooks rules. @biomejs/biome 2.5.14 (2026-09-16), eslint 10.11.0 (2026-09-18), prettier 3.9.9 (2026-09-23; all verified vs npm).21.1M/wk · ships in 1/34
Oxlint + oxlint-tsgolint (type-aware)The third lint lane: type-aware linting is stable (59 of typescript-eslint's 61 type-aware rules) and 22 React Compiler-powered rules are native; it can also host ESLint plugins via jsPlugins. oxlint 1.85.0 (2026-09-21), oxlint-tsgolint 7.0.2002 (2026-09-18; both verified vs npm). See note: OXLINT.29.4M/wk · ships in 8/34
git hooks — husky + lint-staged / lefthookRun lint/format/tests pre-commit or pre-push so issues are caught before CI. husky 9.1.7 (2024-11-18), lint-staged 17.5.1 (2026-09-10), lefthook 2.1.14 (2026-09-14; all verified vs npm).44.2M/wk · ships in 11/34
monorepo task runner — Turborepo / Nx + pnpm workspacesCached, parallel task graph across packages; pnpm 11.11+ also versions and releases the workspace. turbo 2.11.3 (2026-09-22), nx 23.2.1 (2026-09-09), pnpm 12.6.0 (2026-09-22; all verified vs npm). See note: PNPM RELEASE MANAGEMENT; bundler/pnpm 12 depth in RB-E-BUILD.34.2M/wk · ships in 2/34
dependency hygiene — renovate/dependabot + auditAutomated updates + npm/pnpm audit + install-script blocking; RB-E-SECURITY owns the supply-chain thread.
react-doctor (Million)npx react-doctor: one-shot scan for React footguns (state/effects/perf/a11y/security), CI mode, installable as an agent skill; the lint layer for agent-written React (see RB-E-AI-DEVTOOLS). 0.9.14 (2026-09-12, verified vs npm); ~14.9k stars (2026-09-24, verified vs GitHub).2.4M/wk

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

npm weekly downloads (from the corpus's last signals run): husky 44.2M · lefthook 6.2M · @biomejs/biome 21.1M · eslint 198.2M · prettier 168.8M · lint-staged 36.0M · turbo 34.2M · nx 12.4M · lunte 17k · react-doctor 2.4M · oxlint 29.4M · typescript-eslint 112.6M

Verified notes

DX is the feedback loop that keeps the other entries honest: CI gates, lint, hooks and onboarding turn review-time opinions into mechanical checks. The default holds because CI on every PR is the cheapest control that catches everything else, and a local hook moves the same check to before the push. The lint choice is decided by one question — do you need ESLint's react-hooks rules? — since type-aware rules and React Compiler rules are no longer ESLint-exclusive. OXLINT (2026-07-28 and 2026-08-18, verified vs the Oxc release posts; version re-checked 2026-09-24 vs npm): tsgolint v7 made type-aware linting stable, tracking TypeScript 7.0.2 with 59 of typescript-eslint's 61 type-aware rules behind `oxlint --type-aware`, so type-aware rules alone are no longer a reason to keep typescript-eslint. Oxlint 1.79 (2026-08-18) ships 22 React Compiler-powered rules that use the compiler's own validation passes, with the recommended-preset rules mapped into Oxlint's correctness category (replacing the old nursery react/react-compiler rule); react-hooks proper remains ESLint-only. Oxlint 1.83 (2026-09-14, verified vs the Oxc release) updates its lint rules for React 19.3, the React line RB-E-REACT-CORE recommends. The same 2026-08-18 announcement: `oxc-transform-react` applies React Compiler's automatic memoization ~10× faster than the Babel plugin in Oxc's preliminary benchmark, wired into @vitejs/plugin-react 6.1.0 (RB-E-REACT-CORE owns the compiler default). BIOME useReactCompiler (2026-08-13, verified vs the Biome rule docs): 2.5.8 adds `useReactCompiler`, which runs React Compiler in lint mode and surfaces its actionable diagnostics (e.g. a hook called inside a conditional). It only runs when the nearest package.json declares React ≥19, and it sits in the nursery group at `information` severity — off by default, opt in per rule. It narrows the React-rules gap without closing it: read it as Biome's direction, not parity. COHORT SIGNAL: SENTRY RUNS OXLINT, NOT ESLINT (2026-09-10 and 2026-09-24, `react-brain census` deltas, verified vs getsentry/sentry package.json + oxlint.config.ts): getsentry/sentry's lint:js is just `oxlint` (Oxlint 1.83.0), with no typescript-eslint and no ESLint config file at the repo root — the first cohort app on record to stop running ESLint rather than run both. The eslint package (10.8.0) is back in its devDependencies because oxlint.config.ts loads a dozen-plus ESLint plugins (unicorn, import, react, @tanstack/query, boundaries, jest-dom, regexp, react-you-might-not-need-an-effect, e18e) through Oxlint's jsPlugins. Consequence: leaving ESLint the LINTER does not mean leaving the ESLint PLUGIN ecosystem — check whether the plugins you depend on run under Oxlint's JS-plugin host before treating a missing native rule as a blocker. It does not overturn the react-hooks condition (a repo can accept not having those rules), but it is the strongest datapoint that the type-aware + compiler-rule surface suffices for a large TypeScript codebase. PNPM RELEASE MANAGEMENT (2026-07-28, verified vs the pnpm 11.11-11.14 post; pnpm 12 keeps pnpm 11's commands and settings per the 12.0 post, RB-E-BUILD owns pnpm 12): `pnpm change` records changesets-compatible `.changeset/*.md` intents; a bare `pnpm version -r` consumes them across the workspace with dependent propagation through `workspace:` ranges and a committed `.changeset/ledger.yaml` that keeps cherry-picks and merge-backs safe; `pnpm lane` runs per-package prerelease lanes. Config lives under a `versioning` key in pnpm-workspace.yaml; changelogs default to REGISTRY storage (composed at publish time, no committed CHANGELOG.md) unless you set `versioning.changelog.storage: repository`. The same releases add `pnpm doctor` (installation and environment diagnosis with non-zero exit, --json, --offline) and fix a path-traversal vulnerability. For a pnpm monorepo this removes a dependency (changesets) rather than adding one. CROSS-REFERENCES: engineering-principles owns architecture/boundaries depth; RB-E-BUILD owns bundler choice and pnpm 12; RB-E-SECURITY owns supply chain; RB-E-AI-DEVTOOLS owns the agent-written-code review layer. HISTORY: 2026-09-24 CORRECTED: "Sentry has NO eslint" read as "no ESLint run" — eslint is back as a devDependency to host plugins under Oxlint's jsPlugins; Oxlint pin moved 1.83 → 1.85.0. 2026-09-21 REOPENED: Oxlint 1.83 updates its rules for React 19.3. 2026-09-10 REOPENED: census delta — Sentry moved its lint lane to Oxlint. 2026-08-18 WEAKENED: react-compiler linting no longer ESLint-exclusive (Oxlint 1.79). 2026-08-13 REOPENED: Biome 2.5.8 useReactCompiler (nursery). 2026-07-28 WEAKENED: type-aware rules no longer a reason to keep typescript-eslint (tsgolint v7). 2026-07-28 GAP CLOSED: pnpm 11.11+ workspace versioning fires the twir-290 reopen signal.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

Best CI/CD for mobile apps in 2026: a practical comparisonExpo (VENDOR — comparing its own EAS Workflows)

The only structured selection matrix for mobile CI platforms (EAS Workflows vs Bitrise vs Codemagic vs GitHub Actions): fingerprint-based repack turning 10–15min builds into ~2min JS-only builds, and GHA macOS runners measured ~2.4x slower than Codemagic M4s. READ AS A VENDOR DOC — Expo wins its own comparison; the evaluation axes (runner hardware, cache strategy, fingerprint/repack) are the durable part.

Expo without EAS: scaling the React Native DX of an app with 90M+ usersDoctolib

Large-scale case study adopting Expo's bare workflow (not EAS) with fingerprint caching + JS patching to halve CI builds and grow contributors 9→105. A durable enterprise RN build/CI-DX blueprint.

Sources

Depth (in-domain rules) is owned by the engineering-principles skill — this entry is selection breadth.

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About developer experience — CI, lint/format, hooks, monorepo

Diataxis: Explanation. This page builds understanding of the DX feedback loop — the reasoning behind the picks. It is not a setup guide. Architecture/boundary depth is owned by engineering-principles; bundlers by RB-E-BUILD; supply chain by RB-E-SECURITY. Read this for the why.

The one principle that organises everything: an invariant you don't gate is a suggestion

DX isn't comfort — it's the feedback loop that keeps every other decision honest. Every quality property you care about (tests pass, types check, lint is clean, the bundle stays under budget) is either mechanically enforced or it silently rots. The organising principle is therefore brutal and simple: mechanize your invariants. A styleguide nobody lints is a wish; a test suite CI doesn't run is decoration; a "we always typecheck" that isn't gated is a story you tell until the day it isn't true. So the DX question is never "which tools are nice" — it's "what must always be true, and what gate makes it always true?"

The default, and why

Gate tests + typecheck + lint on every PR in CI, and enforce the same locally with a pre-commit hook. For lint/format, default to Biome (one fast tool) unless you need ESLint's react-hooks rules, in which case ESLint flat config + Prettier.

CI on every PR is the single highest-leverage DX baseline — it's the gate that turns invariants from aspiration into fact. The pre-commit hook is the cheap copy of that gate: catch the obvious failures locally so they never burn a CI cycle (and never land). Biome is the default linter/formatter because it's one fast Rust tool doing lint+format with near-zero config; you switch to ESLint flat config + Prettier specifically when you need the react-hooks rules, which are load-bearing in the compiler era (RB-E-REACT-CORE). React Compiler rules alone are no longer that reason: Oxlint 1.79+ ships 22 compiler-powered rules natively, and Biome 2.5.8 added useReactCompiler (nursery, opt-in).

The landscape, and when each piece earns its place

CI (GitHub Actions / similar) — the gate. Tests + typecheck + lint + build on every PR. The non-negotiable baseline; everything else is optimization around it.

Lint/format — Biome vs ESLint(flat)+Prettier — Biome for speed and simplicity (one tool); ESLint+Prettier for the plugin ecosystem (react-hooks, react-compiler, jsx-a11y, import rules). The axis is "do I need specific ESLint plugins?" — if not, Biome.

Oxlint + oxlint-tsgolint — the third lane. Type-aware linting went stable in 2026-07 (oxlint --type-aware, 59 of typescript-eslint's 61 type-aware rules), so keeping typescript-eslint only for type-aware rules no longer holds. Oxlint can also load ESLint plugins through its jsPlugins host: getsentry/sentry runs oxlint as its only JS linter while still using a dozen-plus ESLint plugins that way. Leaving ESLint the linter does not mean leaving the ESLint plugin ecosystem — check that yours load before treating a missing rule as a blocker.

react-doctor — a one-shot scan for React footguns (CI mode or agent skill), worth adding on top of lint when much of the React in a repo is agent-written (RB-E-AI-DEVTOOLS).

Git hooks — husky + lint-staged / lefthook — the local pre-commit/pre-push gate. Run lint/format/affected-tests before code leaves the machine. It's the fast feedback that makes the CI gate rarely fail.

Monorepo task runner — Turborepo / Nx + pnpm workspaces — a cached, parallel task graph so a many-package repo builds and tests incrementally (RB-E-BUILD). The thing that keeps CI fast as the repo grows. Since pnpm 11.11, pnpm also versions and releases the workspace (pnpm change, pnpm version -r, changesets-compatible files), so a pnpm monorepo no longer needs a separate changesets tool.

Dependency hygiene — renovate/dependabot + audit — automated updates plus npm/pnpm audit and install-script blocking. This is where DX and security meet (RB-E-SECURITY); keeping deps current and locked-down is a feedback loop too.

Tradeoffs and failure modes to name out loud

  • Tests but no CI. A real finding in audited production apps: a suite exists but nothing gates it, so invariants quietly rot. Add CI first — it's higher-leverage than any new test.
  • A styleguide that isn't wired into lint. Normative rules enforced by code review are enforced inconsistently and expensively; mechanize them so review can focus on design.
  • Skipping the local gate. Relying only on CI makes the loop slow and noisy; a pre-commit hook catches the trivial failures for free.
  • Biome vs ESLint as identity, not need. Pick on whether you need ESLint's react-hooks rules, not on tribe. Many teams want them — that's a real reason to keep ESLint.
  • Monorepo without a task graph. Running everything on every change doesn't scale; cache and parallelize (Turborepo/Nx) or CI time balloons.

How it interacts with the rest of the stack

  • Build (RB-E-BUILD). The monorepo task graph and bundler choice are the build side of the same loop; DX is the gating/feedback side.
  • Testing (RB-E-TESTING). Tests only protect you if CI runs them — testing tooling and the CI gate are two halves of one mechanism.
  • Security (RB-E-SECURITY). Dependency automation + install-script blocking is shared ground; supply-chain hardening lives in this loop.
  • Architecture depth (engineering-principles). Module boundaries, dependency direction, and what-may-import-what are owned there; DX is how you enforce such rules mechanically.

In one paragraph

DX is the feedback loop that keeps every other choice honest, and its one principle is mechanize your invariants: an unenforced rule is a suggestion. So gate tests + typecheck + lint in CI on every PR, mirror it with a pre-commit hook for fast local feedback, default lint/format to Biome (switch to ESLint+Prettier when you need the react-hooks rules; keep typescript-eslint only if Oxlint's type-aware lane misses a rule you use), and scale a monorepo with pnpm + Turborepo. If an app has tests but no CI, adding CI is the highest-leverage move available — and dependency automation ties this loop to RB-E-SECURITY.


See also: RB-E-BUILD (monorepo task graph, bundlers), RB-E-TESTING (CI gates the suite), RB-E-SECURITY (dependency hygiene / supply chain). Architecture/boundary depth: the engineering-principles skill.

Related in tooling-ops: build · testing · observability · security · ota