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 plugin ecosystem (react-hooks, react-compiler).
has tests but no CI → add CI FIRST; invariants that aren't gated silently rot (a real finding in audited production apps)
needs ESLint react-hooks / react-compiler rules → ESLint flat config + Prettier over Biome (still the only lane carrying them) — though Biome 2.5.8 began closing that gap, see note
kept typescript-eslint ONLY for type-aware rules → re-evaluate: oxlint --type-aware is stable and covers 59/61 of them
multi-package repo → pnpm workspaces + Turborepo for a cached task graph; pnpm 11.11+ also versions and releases the workspace itself (see note) — no separate changesets tool needed
normative styleguide but no linter config → wire the rules into lint so they're enforced mechanically, not by review
lots of agent-written React landing in the repo → add a react-doctor pass (CI or agent skill) 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
lint/format — Biome or ESLint (flat) + Prettier
Biome = one fast Rust tool (lint+format); ESLint flat + Prettier = bigger plugin ecosystem (react-hooks, react-compiler rules)
8.9M/wk · ships in 1/34
Oxlint + oxlint-tsgolint (type-aware)
the third lint lane, and the reason to re-evaluate: TYPE-AWARE linting went STABLE 2026-07-22 with tsgolint v7 — tracks TypeScript 7.0.2 and covers 59 of typescript-eslint's 61 type-aware rules (`oxlint --type-aware`). The rules that needed the type checker are no longer ESLint's exclusive; what still isn't ported is the React plugin surface (react-hooks, react-compiler)
13.6M/wk · ships in 6/34
git hooks — husky + lint-staged / lefthook
run lint/format/tests pre-commit/pre-push; catch issues before CI
cached, parallel task graph across packages (see RB-E-BUILD)
18.7M/wk · ships in 2/34
dependency hygiene — renovate/dependabot + audit
automated updates + npm/pnpm audit + install-script blocking (see RB-E-SECURITY)
react-doctor (Million)
npx react-doctor — one-shot codebase scan for React footguns (state/effects/perf/a11y/security), CI mode + installable as an agent skill; 13.5k★, from the Million.js/react-scan team; positioned as the lint layer for agent-written React (see RB-E-AI-DEVTOOLS)
DX is cross-cutting — architecture/boundaries depth → engineering-principles; bundler choice → RB-E-BUILD; supply-chain → RB-E-SECURITY. This entry is the feedback loop (CI/lint/hooks/ onboarding) that keeps the others honest. BIOME STARTS ON THE REACT-COMPILER GAP (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 self-scopes — it only runs when the nearest package.json declares React ≥19 — but it is in the NURSERY group at `information` severity, i.e. experimental and off by default; you opt in per-rule. That narrows, without closing, the "ESLint is the only lane with react-hooks/react-compiler rules" reason above: treat it as a signal about where Biome is heading, not yet as parity. LINT LANDSCAPE SHIFT (2026-07-28, verified vs the Oxc release post): type-aware linting is no longer a reason to stay on typescript-eslint — tsgolint v7 shipped stable, tracking TypeScript 7.0.2 with 59 of typescript-eslint's 61 type-aware rules behind `oxlint --type-aware`. The remaining ESLint-only surface for React work is the PLUGIN ecosystem (react-hooks, react-compiler), which is what the recommend clause above still keys on. MONOREPO RELEASE MANAGEMENT MOVED INTO pnpm (2026-07-28, verified vs the pnpm 11.11-11.14 post — this fires the twir-290 reopen signal on the merged pnpm RFC, which said "DX/BUILD candidate when it lands in a pnpm release"): `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, and `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`. Same releases add `pnpm doctor` (installation/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.
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 plugin ecosystem (react-hooks, react-compiler).
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 its plugin ecosystem —
notably the react-hooks and react-compiler rules, which are load-bearing in the compiler era
(RB-E-REACT-CORE).
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.
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.
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 plugins (react-
hooks/react-compiler), not on tribe. Many teams want those rules — 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 react-hooks/react-
compiler plugins), 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.