Developer Tools

Oxlint vs Biome vs ESLint in 2026: Benchmarked on a Real Next.js Codebase

I ran ESLint 9 and 10, Oxlint 1.88 and Biome 2.5 on this site's real Next.js code. Oxlint was 81× faster than ESLint with the same rules and matched all 80 typescript-eslint findings 16× faster. Here's what each migration keeps and breaks.

Oxlint vs Biome vs ESLint cover: terminal showing eslint done in 9.56s, biome lint done in 131ms and oxlint done in 118ms on a 403-file Next.js repo
On this page
  1. TL;DR: should I replace ESLint with Oxlint or Biome?
  2. What are Oxlint, Biome and ESLint?
  3. How I benchmarked them
  4. Is Oxlint faster than ESLint?
  5. Is Biome faster than ESLint?
  6. How well does the ESLint migration work?
  7. Type-aware linting: Oxlint vs typescript-eslint
  8. Install size
  9. Which should I use in 2026?
  10. How do I switch from ESLint to Oxlint?
  11. Common mistakes when switching linters
  12. Key takeaways

Oxlint vs ESLint is no longer a question of whether the Rust linter is faster. It is, by a lot: on this site's real Next.js codebase, Oxlint 1.88 ran the same rules as my ESLint config in 118 ms instead of 9.6 seconds, 81× faster. Biome 2.5 was almost as quick at 131 ms. The real questions are which rules you lose, which new warnings you gain, and whether the type-aware rules hold up. I measured all of that.

I took the source of fabwaseem.com (403 source files, about 47,000 lines of TypeScript and React), ran ESLint 9 and 10 with the official Next.js config, migrated that config to Oxlint and to Biome with their own migration tools, and compared speed, memory, findings and install size. Type-aware linting got its own round, because that's where most of the ESLint time goes.

TL;DR: should I replace ESLint with Oxlint or Biome?#

For most TypeScript and Next.js projects in 2026, Oxlint is the easiest switch: its migration tool converted 79 of 86 rules from my ESLint config, it honours your existing eslint-disable comments, and its type-aware mode found every one of the 80 issues that typescript-eslint found, 16× faster. Biome makes sense if you also want to replace Prettier with a single fast tool, but its migration left 43 rules behind, including all of React's new compiler rules. Keep ESLint if you depend on plugins neither tool covers.

Real Next.js repo, 403 files, 8 vCPUWall timeCPU timePeak memoryRules from my ESLint config
ESLint 9.39.5, Next.js config9.56 s18.0 s1,018 MBall
ESLint 9 with --concurrency=auto5.02 s27.4 s3,664 MBall
ESLint 9 with --cache, nothing changed0.93 s1.4 s332 MBall
ESLint 10.13.0, same config9.59 s17.9 s1,046 MBall
Oxlint 1.88.0, migrated config118 ms387 ms193 MB79 of 86 migrated
Biome 2.5.15, migrated config131 ms556 ms75 MB70 of 114 migrated
Oxlint, defaults (no config)72 ms95 ms148 MBn/a
Biome, recommended preset191 ms1,026 ms78 MBn/a

The two tools count rules differently (Biome also counts rules that are turned off), so compare each row with its own migration report.

What are Oxlint, Biome and ESLint?#

  • ESLint is the JavaScript linter almost every project starts with. It's written in JavaScript, has thousands of plugins, and is what eslint-config-next and typescript-eslint plug into. ESLint 10 is the current major. It had 159 million npm downloads in the week of October 6 to 12, 2026.
  • Oxlint is the linter from the Oxc project, written in Rust. It reimplements popular ESLint rules and plugins (typescript, react, react-hooks, jsx-a11y, import, unicorn, nextjs and more) natively, reads ESLint's disable comments, and adds type-aware rules through tsgolint, which runs on typescript-go. 27 million downloads that week. NestJS 12 now scaffolds new projects with Oxlint, as I found in my NestJS 12 upgrade guide.
  • Biome is a Rust toolchain that combines a linter and a Prettier-compatible formatter in one binary. It has its own rule names and its own type inference instead of using the TypeScript compiler. 18 million downloads that week.

How I benchmarked them#

  1. Codebase. The real source of this site at the commit that added change history: a Next.js 16.4 app with React 19.3, Prisma, an admin panel and an MCP server. 403 source files and about 47,000 lines. ESLint's file count in my runs was 406 because it also linted my three small benchmark config files; Biome counted 413 files.
  2. ESLint config. The project's own config: eslint-config-next core-web-vitals plus its TypeScript preset. That's what create-next-app generates. For the type-aware round I added typescript-eslint's recommendedTypeCheckedOnly with projectService.
  3. Migration. npx @oxlint/migrate (oxlint-migrate) for Oxlint and biome migrate eslint (Biome's migration guide) for Biome, both run against the same ESLint config with no hand edits.
  4. Machine. The same 8 vCPU Intel Xeon VM with 15 GB of RAM I used for my Hono vs Express benchmark. All tools could use all 8 cores, because Oxlint and Biome are multi-threaded by design. I also ran single-threaded versions.
  5. Method. hyperfine, 5 runs for ESLint and 10 for the Rust tools, after warm-up runs. I report the median wall time. Node.js 24.22.0. Versions were the latest npm releases on October 18, 2026: ESLint 10.13.0 (plus the project's 9.39.5), Oxlint 1.88.0 with oxlint-tsgolint 7.0.2004, Biome 2.5.15, typescript-eslint 8.71.2, eslint-config-next 16.4.2.

Is Oxlint faster than ESLint?#

Yes, by about two orders of magnitude. With the migrated config, Oxlint linted the whole repo in 118 ms against 9,555 ms for ESLint 9, which is 81× faster. ESLint 10 was no faster than ESLint 9 on this codebase (9,586 ms).

Oxlint vs Biome vs ESLint lint time and memory on a real Next.js codebase

ESLint has two ways to close the gap, and neither gets close:

  • --concurrency=auto, ESLint's multi-threaded mode, nearly halved the time to 5.02 s, but peak memory went from 1.0 GB to 3.7 GB. Oxlint was still 43× faster.
  • --cache brought an unchanged repo down to 0.93 s. That helps locally, but CI usually starts with an empty cache, and Oxlint's cold run was still 8× faster than ESLint's warm one.

Even on a single thread (--threads=1), Oxlint took 376 ms. Most of its advantage comes from being native code that parses each file once, not from using more cores.

Is Biome faster than ESLint?#

Also yes. Biome ran the migrated config in 131 ms, 73× faster than ESLint, and used the least memory of anything I tested: 75 MB. With its full recommended preset, which has many more rules, it took 191 ms. On a single thread the recommended preset took 982 ms.

So Oxlint vs Biome on speed is close to a tie for normal linting. On this repo Oxlint was about 10% faster with similar rule counts, and Biome used less memory. If speed is the only reason you're switching, either one wins. The differences that matter are in rule coverage and migration.

How well does the ESLint migration work?#

This is where the two tools differ most.

Migrating this repo's ESLint configOxlint (@oxlint/migrate)Biome (biome migrate eslint)
Rules migrated7970 (+1 nursery)
Rules left behind743
React Compiler rules (react-hooks v7)ported, except config and gatingnone of the 14
Next.js rulesall but 111 unknown to Biome
Reads existing eslint-disable commentsyesno (uses biome-ignore)
New findings on a repo that's clean in ESLint124

Oxlint left out only 7 rules, and most of those are obsolete anyway: react/jsx-uses-react, for example, isn't needed with the modern JSX transform. It reported one new error that ESLint didn't: react(purity) on const year = new Date().getFullYear() inside a component. ESLint's react-hooks/purity rule was on in the same config and didn't flag it, so the ports don't behave identically yet. It's a fair warning, but I'd treat edge cases in the compiler rules as "check by hand".

Biome couldn't migrate 32 rules it hasn't implemented, including all 14 React Compiler rules from eslint-plugin-react-hooks 7 (purity, refs, set-state-in-effect and the rest), plus 11 @next/next rules it doesn't know. Its 24 new findings on a repo ESLint says is clean break down like this:

  • 15 from useExhaustiveDependencies, Biome's version of exhaustive-deps. Most of them complain about extra dependencies. One flagged useEffect(..., [pathname]) in a skip-link component, where the dependency is there on purpose so the effect re-runs on every navigation. Biome marks the fix as unsafe, rightly: applying it would change behaviour.
  • 7 on lines that already had // eslint-disable-next-line. Biome ignores ESLint comments, so you either convert them to biome-ignore or live with the noise.
  • 2 others, a motion.img reported as a raw <img> and an ARIA attribute on a role that doesn't support it.

None of that is wrong exactly, but it's a day of cleanup that the Oxlint migration doesn't need. I wrote about the React Compiler, and why its lint rules matter, in React Compiler in Next.js 16.4.

Type-aware linting: Oxlint vs typescript-eslint#

Type-aware rules, such as no-floating-promises and no-misused-promises, need the TypeScript compiler's view of your types. They catch real bugs, and in ESLint they are slow because typescript-eslint has to build a full TypeScript program in JavaScript.

Type-aware lint, same rulesWall timePeak memoryIssues found
ESLint 10 + typescript-eslint recommendedTypeCheckedOnly20.5 s2,344 MB81 at 80 locations
Same, with --concurrency=auto11.4 s5,726 MB81
Oxlint --type-aware, migrated config1.29 s893 MBall 80, plus 13 more

Oxlint was 16× faster and found every one of typescript-eslint's 80 locations, rule for rule: 17 unnecessary type assertions, 11 require-await, 11 no-base-to-string, 6 misused promises, 5 await-thenable and so on. That's an impressive level of parity. The Oxlint docs say it supports 59 of typescript-eslint's 61 type-aware rules.

Migration coverage and type-aware lint parity between Oxlint, Biome and ESLint

The 13 extra findings are the interesting part. One was the purity warning from above. The other 12 were all in three route files under app/.well-known/, the folder that serves this site's OAuth metadata. Oxlint reported no-unsafe-call and no-unsafe-return because it saw every import there as an unresolved error type.

I tracked it down. The tsconfig uses "include": ["**/*.ts", "**/*.tsx"]. Plain tsc includes those dot-folder files, but tsgolint, which uses typescript-go, left them out of the program, so the @/ path alias didn't resolve. Copying the same file into a normal folder made the errors disappear. Adding "app/.well-known/**/*.ts" to include fixed it completely. If you use Next.js metadata routes or other dot folders, check for this before you trust the type-aware output.

Oxlint can also replace tsc --noEmit: oxlint --type-aware --type-check reports TypeScript errors alongside lint results. On this repo it took 1.75 s against 7.89 s for tsc --noEmit. It also reported the same .well-known files as "Cannot find module" while tsc passed cleanly, which confirms the cause. The docs list TypeScript 7 as the supported version for type-aware mode; this project is still on TypeScript 5.9 and everything else worked. I covered the native compiler in TypeScript 7.

Biome doesn't use the TypeScript compiler at all. Its own type inference powers rules such as noFloatingPromises and noMisusedPromises, both still in the nursery group in 2.5.15. On this repo they found 1 of the 2 floating promises and none of the 6 misused promises that typescript-eslint and Oxlint found, plus one different spot. It's a promising approach, but not a replacement for typed linting yet.

Install size#

Fresh npm installPackagesSize on disk
ESLint 10 + eslint-config-next 16.4.2312116.5 MB (incl. 23.6 MB TypeScript peer)
ESLint 10 + typescript-eslint9546.1 MB
Oxlint216.9 MB
Oxlint + oxlint-tsgolint (type-aware)438.5 MB
Biome264.4 MB

Biome's binary is bigger because it includes a formatter and support for CSS, JSON, GraphQL and more. Both Rust tools install in seconds and have no plugin dependency tree to keep up to date.

Which should I use in 2026?#

If you...UseWhy
Want the fastest lint with the least migration workOxlint79 of 86 rules migrated, reads eslint-disable comments, 81× faster here
Rely on type-aware rulesOxlint --type-awareSame 80 findings as typescript-eslint, 16× faster
Want one tool for linting and formattingBiomeReplaces ESLint and Prettier; lowest memory
Use React Compiler rules heavilyOxlint or ESLintBiome has none of the 14 yet
Depend on niche ESLint pluginsESLint, or Oxlint plus ESLintRun Oxlint first and keep ESLint only for the rules it lacks
Have a huge monorepo and slow CIOxlint first, ESLint for leftoversCuts most of the lint time immediately

A common middle path is to run both: Oxlint for everything it supports, and ESLint with only the leftover rules (eslint-plugin-oxlint turns off the rules Oxlint already covers). You get most of the speed without losing any rules.

How do I switch from ESLint to Oxlint?#

bash
# 1. generate .oxlintrc.json from your ESLint flat config
npx @oxlint/migrate eslint.config.mjs --type-aware --details

# 2. add the tools
npm i -D oxlint oxlint-tsgolint

# 3. run it (add --type-check to replace tsc --noEmit)
npx oxlint --type-aware

Then check three things before you delete ESLint. Read the --details list of skipped rules and decide whether you need any. Look at new findings in files outside your tsconfig's normal folders, like .well-known. And keep ESLint in CI for a week, running both, so you can compare results on real pull requests.

For Biome, npx @biomejs/biome init followed by npx @biomejs/biome migrate eslint --write (and migrate prettier --write if you use Prettier) does the same job. Budget time to convert eslint-disable comments and review useExhaustiveDependencies findings by hand.

Common mistakes when switching linters#

  • Comparing default configs. Oxlint's defaults run a small set of correctness rules, which is why they take 72 ms. Compare the migrated config against your ESLint config, not defaults against defaults.
  • Applying unsafe fixes in bulk. Biome's dependency-array fixes can change when effects run. Review them one by one.
  • Forgetting ESLint doesn't read .gitignore. This repo's ESLint config also lints the 23 generated Prisma files in generated/ after pnpm install, because flat config ignores only what you list. Oxlint and Biome skip ignored files by default. Add generated/** to your ESLint ignores or use includeIgnoreFile.
  • Trusting type-aware output blindly. Unresolved types show up as no-unsafe-* errors. If a whole folder lights up, check your tsconfig include first.
  • Dropping ESLint before checking plugin coverage. Run the migration tool's report and look for rules you actually rely on.
  • Benchmarking only on a laptop. CI machines are slower and start with an empty cache. Measure where the time actually goes.

Related: if you're modernising a Node backend at the same time, my Bun vs Node.js benchmark covers the runtime side.

Key takeaways#

  • On a real 403-file Next.js codebase, Oxlint 1.88 ran my migrated ESLint rules in 118 ms and Biome 2.5 in 131 ms, against 9.56 s for ESLint 9. That's 81× and 73× faster.
  • ESLint's --concurrency=auto halved its time to 5.02 s but used 3.7 GB of memory; a warm --cache run took 0.93 s, still 8× slower than a cold Oxlint run.
  • Oxlint's migration kept 79 of 86 rules and read existing eslint-disable comments. Biome's kept 70 and left 43 behind, including all 14 React Compiler rules.
  • On a repo that's clean in ESLint, the migrated Oxlint config reported 1 new error and Biome reported 24, mostly dependency-array warnings and ignored eslint-disable comments.
  • Oxlint --type-aware matched all 80 typescript-eslint findings in 1.29 s versus 20.5 s, but reported 12 false positives in a .well-known dot folder until I listed it in tsconfig include.
  • oxlint --type-aware --type-check replaced tsc --noEmit in 1.75 s versus 7.89 s.
  • Choose Oxlint for the easiest ESLint replacement, Biome for one tool that lints and formats, and keep ESLint only for plugins neither covers.

Questions