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
option
tradeoff
evidence
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) + Prettier
Biome 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 / lefthook
Run 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).
Cached, 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.
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).
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.
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.
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.
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.