oxlint 1.87.0 vs ESLint 9.39.4 on react-hook-form (301 files): 0.09 s vs 9.81 s single-thread (6.02 s with --concurrency auto, 1.10 s warm --cache). @oxlint/migrate ported 42 of 84 rules natively, 80 with JS plugins. 315 vs 562 warnings; seeded test 11/15 native, 13/15 with JS plugins, ESLint 15/15.
7 min read
On react-hook-form's 301 lintable files, ESLint 9.39.4 with the repo's own config took a median 9.8 s, and oxlint 1.87.0 with a migrated config took 0.09 s. The catch: only 42 of the 84 ESLint rules moved over natively, and oxlint reported 315 warnings where ESLint reported 562.
Short answer: switch if your config is mostly core, TypeScript and React rules, and keep ESLint (or run both) if you lean on type-aware rules or plugins oxlint cannot run natively. No affiliate links in this post.
91856c2 (4 Oct 2026), installed from its own lockfile. Its flat config uses typescript-eslint 8.61.1 recommended, eslint-plugin-react, react-hooks, simple-import-sort, eslint-plugin-prettier and eslint-plugin-playwright.eslint . with no cache unless noted.@oxlint/migrate 1.86.0 twice: once native-only (--js-plugins=false) and once with JS plugins, which loads ESLint plugins oxlint lacks..tsx file with 15 planted problems, one for each kind of rule in the config, then deleted.no-require-imports errors ESLint never shows. Keeping .oxlintrc.json in the repo root fixed it.| Setup | Rules | Median | Runs |
|---|---|---|---|
| ESLint 9.39.4, repo config, single thread | 84 | 9.81 s | 9.81 / 9.65 / 9.90 |
ESLint, --concurrency auto | 84 | 6.02 s | 7.27 / 6.02 / 5.51 |
ESLint, prettier/prettier turned off | 83 | 5.66 s | 5.66 / 5.22 / 6.28 |
ESLint, --cache with nothing changed | 84 | 1.10 s | 1.10 / 1.22 / 0.92 |
| oxlint, native rules only | 42 | 0.09 s | 0.31 / 0.09 / 0.09 |
| oxlint, native + simple-import-sort as a JS plugin | 79 | 0.61 s | 0.69 / 0.60 / 0.61 |
| oxlint, native + all JS plugins (incl. Prettier) | 80 | 4.75 s | 5.17 / 4.67 / 4.75 |
Rule counts are as @oxlint/migrate reported them. Native oxlint was about 100x faster than a single-threaded ESLint run. That ratio drops fast once you add JS plugins: running Prettier as a lint rule inside oxlint cost about 4 s, almost the same as running prettier --check 3.8.4 on the same 301 files (4.13 s median). Formatting through the linter is the slow part in both tools.
Two ESLint numbers matter for a fair comparison. Turning on ESLint's built-in multithreading (--concurrency auto) took 9.8 s down to 6.0 s with no other change. And with --cache, an unchanged repo re-linted in 1.1 s, so local re-runs were never the 10 s problem. CI runs without a warm cache are where oxlint pays off.
On the real repo, every one of oxlint's 307 unused-variable warnings matched an ESLint warning at the same line and column. Its other 8 warnings (4 react/display-name, 4 Playwright) matched ESLint's per-rule counts. The 247 warnings only ESLint raised were all one pattern in the repo's type-test files: "'actual' is assigned a value but only used as a type." That is a typescript-eslint extension of no-unused-vars, and oxlint's version did not flag it.
The seeded file told the same story:
| Planted problem | ESLint | oxlint + JS plugins | oxlint native |
|---|---|---|---|
Hook called inside if | yes | yes | yes |
useEffect missing a dependency | yes | yes | yes |
console.log | yes | yes | yes |
Type used without import type | yes | yes | yes |
| Unused variable | yes | yes | yes |
| Unused type alias | yes | yes | yes |
!!x in a condition | yes | yes | yes |
// @ts-ignore | yes | yes | yes |
var | yes | yes | yes |
let never reassigned | yes | yes | yes |
Anonymous memo() component | yes | yes | yes |
| Unsorted imports | yes | yes | no |
| Formatting off (Prettier) | yes | yes | no |
| Value only used as a type | yes | no | no |
| Anonymous default-export component | yes | no | no |
| Total | 15 / 15 | 13 / 15 | 11 / 15 |
The two misses that matter here are both quiet: neither breaks a build, but a team that relies on react/display-name for DevTools names or on the type-only check will lose them without noticing. Check the typescript-eslint 8.71 post if your config uses rules beyond recommended.
@oxlint/migrate read all 84 rules from the flat config. Native-only, it ported 42 and skipped 42: 38 belong to plugins oxlint does not ship (Playwright, Prettier, simple-import-sort), 3 it lists as unsupported (react/jsx-uses-react, react/jsx-uses-vars, react/no-deprecated), and 1 is still a nursery rule (react/require-render-return). With JS plugins on, it ported 80. It also warned that react.version: "detect" and react.pragma settings do not carry over.
JS plugins are still marked alpha in the oxlint docs, and they cannot run type-aware rules yet. If your config uses recommendedTypeChecked, oxlint native alone will not replace it today. The oxlint 1.87 release notes post covers what that version added for React and jsx-a11y.
npx @oxlint/migrate eslint.config.mjs --details and read the skipped list before you change anything..oxlintrc.json in the repo root, not elsewhere, so ignores and overrides resolve the same way.eslint-plugin-oxlint exists for exactly this: it turns off the ESLint rules oxlint already covers.| Situation | My pick |
|---|---|
Config is mostly core, typescript-eslint recommended, React and hooks rules | oxlint native, with formatting as a separate step |
| Heavy use of type-aware rules | Keep ESLint, or run oxlint first and ESLint for the type-aware set |
| Plugins oxlint does not ship (Playwright, import sorting) | oxlint with JS plugins, or both tools side by side |
| Vue, Svelte or Angular templates | Keep ESLint for now; JS plugins do not handle custom parsers yet |
| ESLint is slow only on your laptop | Try --cache and --concurrency auto first |
Bottom line: on this repo oxlint went from 9.8 s to 0.09 s and agreed with ESLint on every warning it raised, but it skipped half the rules natively and silently dropped two checks. Who should not switch yet: teams whose lint config is mostly type-aware rules or framework template plugins. Everyone else can get most of the speed by running both and letting oxlint go first.
How this was made: I ran every command above on the ShopperCove test box and kept the raw JSON output; the write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 5 Oct 2026 ~22:25-22:35 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 24.21.0). Repo react-hook-form @ 91856c2 (4 Oct 2026), pnpm install from repo lockfile. ESLint 9.39.4 (repo pin), typescript-eslint 8.61.1, eslint-plugin-react 7.37.5, react-hooks 7.1.1, eslint-plugin-prettier 5.5.6, prettier 3.8.4. oxlint 1.87.0, @oxlint/migrate 1.86.0 (native-only --js-plugins=false: 42 rules, 42 skipped = 38 JS-plugin rules [playwright, prettier, simple-import-sort], 3 unsupported [react/jsx-uses-react, jsx-uses-vars, no-deprecated], 1 nursery [react/require-render-return]; JS-plugins mode: 80 rules, jsPlugins eslint-plugin-prettier + simple-import-sort). Wall time, 3 interleaved runs: eslint . 9.81/9.65/9.90; --concurrency auto 7.27/6.02/5.51; prettier off 5.66/5.22/6.28; --cache warm 1.10/1.22/0.92; oxlint native 0.31/0.09/0.09; oxlint + simple-import-sort JS plugin 0.69/0.60/0.61; oxlint + all JS plugins 5.17/4.67/4.75; prettier --check 300 files 4.14/4.13/4.00. Findings: ESLint 301 files 0 errors 562 warnings (554 no-unused-vars, 4 display-name, 2+2 playwright); oxlint 315 warnings (307 no-unused-vars all matching ESLint line+col, 4 display-name, 4 playwright). 247 ESLint-only = 'assigned a value but only used as a type' in src/typetest. Seeded 15-problem .tsx: ESLint 15/15, oxlint+JS 13/15 (misses only-used-as-type, anonymous default export display-name), native 11/15 (also misses import sort, prettier). Gotcha: config outside repo root -> 311 files + 4 no-require-imports errors (paths resolve relative to config). Seed file deleted; repo unchanged except untracked .oxlintrc-*.json in bench clone. Not tested: ESLint 10.12, oxlint type-aware, Vue/Svelte, editor, autofix. No affiliate.
Biome 2.5.15 check vs ESLint 9.39.4 + Prettier 3.8.4 on react-hook-form (317 files): 0.41 s vs 18.7 s sequential stack (ESLint 13.58 s, Prettier check 5.22 s). migrate eslint: 54/476 direct, ~50% covered. Biome 200 errors/427 warnings vs ESLint 0/562; 9 format diffs; 287 noExplicitAny from recommended.
Summary5 Oct 20266 min read
oxlint published v1.87.0 on 5 October 2026: suggestion support for three React/unicorn rules and jsx-a11y/label/mouse/lang hardening plus other fixes. npm latest 1.87.0 confirmed; ShopperCove docs checklist only; no affiliate.
Summary5 Oct 20264 min read
ESLint published v10.12.0 on 2 October 2026 as a minor release: documentation and TypeScript types for SourceCode#getText(), getLoc(), and getRange() now accept tokens and comments (matching runtime behavior), plus multiple rule fixes including astral/Unicode letter handling and autofix edge cases. npm latest confirmed 10.12.0; ShopperCove docs checklist only; no affiliate.
Summary5 Oct 20264 min read