TypeScript

oxlint vs ESLint: Why I Replaced ESLint with oxlint

Replaced ESLint with oxlint in a React TypeScript project and cut lint time from 12 seconds to under one. What I traded away and whether it was worth it.

10 May 2026

oxlint vs ESLint: Why I Replaced ESLint with oxlint

My pnpm lint used to take about 12 seconds on this project. Not catastrophic, but long enough that I'd switch to another tab while it ran. Long enough that I started skipping it locally and leaning on CI to catch things. That's a habit I didn't want to build.

So I replaced ESLint with oxlint and the same pass now takes under a second. That's the whole story, really. The rest is just the details of how I got there and what I gave up.

What oxlint actually is

oxlint is a linter built on top of oxc, a JavaScript toolchain written in Rust. It runs natively, processes files in parallel, and doesn't carry the overhead of Node.js plugin loading. The oxc team built it specifically to address ESLint's performance ceiling; ESLint's plugin architecture was designed for correctness and extensibility, not for speed at scale.

oxlint ships with its own implementations of the most commonly-used ESLint rules: the core rules, eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-unicorn, eslint-plugin-jsx-a11y, and a growing set of TypeScript rules that mirror @typescript-eslint. You don't install plugins. You don't configure a parser. You run one binary.

The coverage is not complete. That's the honest trade-off. But for most projects doing standard React + TypeScript work, the covered rules are the rules that actually matter day-to-day.

Why ESLint was slow here

The culprit wasn't ESLint itself, it was the TypeScript plugin. @typescript-eslint in type-aware mode (parserOptions.project) needs to boot up a full TypeScript language service to evaluate certain rules. Rules like no-floating-promises and await-thenable need type information, so they pay that cost. When you have a hundred-something files and TypeScript is running full type checking in the background just to support linting, those seconds add up.

I wasn't even using most of the type-aware rules. I had them on because they were in the recommended config and I'd never audited whether I actually needed them. That's a common way to accumulate linting overhead: install a recommended config, never revisit it. The same pattern shows up in JavaScript performance generally: you add something from a starter template and it silently costs you until you look.

The actual migration

The migration was one afternoon. Not because it was complicated, but because it required me to think clearly about which rules I actually needed.

I listed every rule I had enabled in my old .eslintrc and went through the oxlint rule coverage page to see what mapped over. Most did. The ones that didn't fell into two buckets: rules I could drop (I'd copy-pasted them from a starter config years ago), and a small handful I genuinely used.

For the rules I couldn't drop, I had a choice. I could run ESLint alongside oxlint for those specific rules, or I could accept the reduced coverage and monitor manually. I ended up removing most of them. The ones I'd flagged as genuine turned out to be things TypeScript's own type system was already enforcing.

The final setup is a single command in package.json:

Bash
oxlint --ignore-path .gitignore .

No config file, no plugins to manage, no version-mismatch issues between @typescript-eslint/parser and @typescript-eslint/eslint-plugin. It just runs.

If you want to configure specific rules, oxlint supports an oxlintrc.json file. I haven't needed it yet.

What I gave up

Custom rules. If your team has project-specific lint rules enforced via ESLint plugins, oxlint can't run them. oxlint doesn't have a plugin API yet. That's a real constraint.

The type-aware rules that need a TypeScript language service also aren't there. no-floating-promises is the one I see people miss most. If you rely on that heavily, factor it in before switching. Worth noting that TypeScript itself will surface many of these issues during compilation; if you're not already using TypeScript's compiler as a debugging layer, now is a good time to start.

Auto-fix support is also more limited in oxlint right now. ESLint's --fix flag covers more cases. oxlint does fix some rules automatically, but if you're used to running eslint --fix as part of your save-on-edit workflow, you'll notice gaps.

Finally, the ecosystem. ESLint has years of integrations: editor plugins, pre-commit hooks, CI reports, shared configs from major frameworks. oxlint is catching up but it's not there yet. The VS Code extension exists and works, but it's less mature than the ESLint extension.

Whether it was worth it

For this project, yes. It's a personal site with a well-understood codebase and no team-specific lint rules. The 12-second lint pass was real friction, and eliminating it made me lint more consistently.

For a team with shared custom rules and heavy reliance on type-aware checks, I'd be more cautious. Not because oxlint is bad, but because the migration requires you to actively decide what you're trading away. That decision is harder when multiple people depend on the rules.

The thing I keep coming back to is this: the ESLint config I had was not something I'd consciously designed. It was layers of "recommended" configs accumulated over years, most of which I didn't understand in detail. Switching to oxlint forced me to actually read what I was enforcing. That audit was worth doing regardless of what I ended up running.

Speed is real. But the clarity that came from stripping the config down to what I actually care about was the bigger win.


If you're moving toward a staff or architect role, decisions like this, knowing when to replace a tool versus when to tune it, are exactly the kind of trade-off thinking that matters. My System Design Mastery course covers this kind of reasoning across infrastructure and architecture decisions. And if you want these posts before they hit the site, I write about them in the Monday BY Gazar newsletter.

Keep reading