Privacy Policy© 2026 DEV BAK - TECH BLOG. All rights reserved.
DEV BAK - TECH BLOG
frontend

ESLint vs Biome vs Oxlint — Speed, Ecosystem, and Migration Cost Comparison for Frontend Linting Tools in 2026

A linter is a tool that catches potential bugs, bad patterns, and style inconsistencies before your code runs. If you've ever had a mistake slip through to production that a linter would have caught during review, you know exactly why CI pipeline linting matters.

Honestly, I spent a long time thinking "ESLint is good enough." That changed when I watched ESLint run for over 60 seconds in a monorepo (a structure where multiple packages are managed in a single repository) CI, and I started seriously looking for alternatives — only to find the ecosystem had changed more than I expected. ESLint v10 dropped the legacy configuration format entirely, and I saw firsthand how Rust-based Biome and Oxlint cut tens of seconds down to just a few in large codebases.

This article aims to compare the three tools — ESLint, Biome, and Oxlint — across speed, ecosystem, and migration cost as of 2026, and to help you establish selection criteria based on your project's situation. Whether you're starting a new project, heavily dependent on legacy plugins, or hitting a CI speed bottleneck — by the end of this article, you'll be able to pick the right one for your situation.


Core Concepts

The 2026 Linting Ecosystem Landscape

Here's a quick summary of where each tool sits.

Item ESLint Biome Oxlint
Implementation Language JavaScript (Node.js) Rust Rust
Role Linter Linter + Formatter Linter (formatter is separate Oxfmt)
Relative Speed Baseline (1×) ~15–20× faster ~50–100× faster
Built-in Rule Count Hundreds + unlimited plugins 500+ (v2.5) 700+
Configuration Complexity High Very low Low
Plugin Ecosystem Best in the industry Early stage (GritQL) JS plugin alpha stage
Latest Stable Version v10 (February 2026) v2.5 (June 2026) v1.0 (August 2025)

Speed figures are based on benchmarks for a ~100k LOC codebase on an M1 Pro. Actual differences may vary depending on the number of enabled rules and project structure.


What ESLint v10 Changed

ESLint is a tool with an overwhelming ecosystem built up since 2013. Its 130 million weekly npm downloads illustrate just how deeply embedded it is today.

The biggest change in v10 is the complete removal of the legacy .eslintrc configuration format. Flat config (eslint.config.js) became the default in v9, and v10 removes any way to go back.

What is Flat Config? It consolidates the configuration that was previously spread across multiple files like .eslintrc.json and .eslintrc.js into a single eslint.config.js file. Instead of merging configurations by traversing up the directory hierarchy, everything is explicitly controlled from one file.

The problem is that major plugins like eslint-plugin-react and eslint-config-next have adopted flat config support at varying speeds. Issue trackers are full of reports of installation conflicts right after upgrading to v10, and with ESLint v9.x EOL scheduled for August 2026, the entire ecosystem is under migration pressure.


Biome — Linter and Formatter in One

Biome started as a project originally called Rome. The core idea is to combine a linter and formatter into a single binary, handling both linting and formatting through a single configuration file (biome.json).

If you've ever wrestled with configuring ESLint and Prettier separately and keeping them from conflicting, you'll immediately understand why this approach is appealing.

Since its v2.0 "Biotype" release in late 2025, it has maintained a very fast release cycle. v2.5 added 500 rules, cross-file linting (detecting unused CSS classes, etc.), and --watch mode all at once.

It adopted GritQL as its own plugin system — a way to write custom lint rules by expressing JavaScript/TypeScript code patterns like a query language. Unlike ESLint's AST (Abstract Syntax Tree)-based plugins, which implement rules by directly traversing the tree in JavaScript, GritQL takes a more declarative approach through pattern matching syntax. The concept is interesting, but it's still a long way from the thousands of battle-tested packages available in the ESLint plugin ecosystem.


Oxlint — Optimized for Speed

Oxlint is the linter component of the OXC (The JavaScript Oxidation Compiler) project, led by Vercel and closely associated with the Vite core team. It released its v1.0 stable version in August 2025 and has been adopted in production by large companies like Shopify, Airbnb, and Mercedes-Benz.

It features 700+ built-in rules and TypeScript type-aware linting.

What is type-aware linting? Regular linting only analyzes the syntactic structure (AST) of code, while type-aware linting also leverages TypeScript's type information to apply more precise rules. For example, it can catch bugs like calling .then() on a non-Promise value, or detect patterns where return values of async functions are unintentionally ignored. @typescript-eslint has 61 type-aware rules in total, and Oxlint already supports 59 of them.

In March 2026, it released an alpha JavaScript plugin API, narrowing the compatibility gap with the ESLint plugin ecosystem.


Practical Application

Example 1: ESLint Flat Config — Existing Projects with Heavy Plugin Dependencies

If your project uses official framework ESLint plugins for Next.js, NestJS, Storybook, etc., there's honestly no realistic alternative to ESLint right now. The fact that @typescript-eslint/eslint-plugin records 106 million weekly downloads illustrates the level of dependency.

Here's an example of flat config setup in an ESLint v9+/v10 environment.

javascript
// eslint.config.js (flat config, ESLint v9+/v10)
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
 
export default [
  {
    ignores: ['dist/**', 'node_modules/**', '**/*.d.ts'], // Specify here instead of an .eslintignore file
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: { parser: tsParser },
    plugins: { '@typescript-eslint': tsPlugin },
    rules: {
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    },
  },
];
Component Description
ignores Replaces the legacy .eslintignore file. In flat config, you specify patterns directly in this array
files Specifies the file patterns this configuration applies to
languageOptions.parser Specifies the parser for TypeScript parsing
plugins Registers plugins by directly importing the plugin object. This differs from the string-based registration of the legacy approach

Example 2: Biome — New Project, Integrated Linter·Formatter Setup

Biome's biggest strength is that you barely need to touch configuration files. A single biome.json handles both linting and formatting.

json
{
  "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedVariables": "error"
      },
      "css": {
        "noUnusedClasses": "error"
      }
    }
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "trailingCommas": "all"
    }
  }
}
bash
# Check and auto-fix linting + formatting in one pass
npx @biomejs/biome check --write .
 
# Watch mode for file changes (real-time checking during development)
npx @biomejs/biome check --watch .
 
# Automatically convert existing .eslintrc + .prettierrc to biome.json
npx @biomejs/biome migrate eslint
npx @biomejs/biome migrate prettier

The cross-file linting (noUnusedClasses) added in v2.5 tracks whether classes defined in CSS files are actually used in JSX or HTML, crossing file boundaries. While traditional linting analyzed each file independently, this represents an evolution toward understanding relationships between files.

80–90% of existing ESLint rules map directly to Biome rules. The remaining 10–20% may require manual handling or have to be dropped — framework-specific rules like Next.js's @next/next plugin rules fall into this category.


Example 3: Oxlint — Resolving CI Speed Bottlenecks, Parallel Execution Strategy with ESLint

For a 100k LOC monorepo, Oxlint handles in under 2 seconds what ESLint takes 45–90 seconds to process. I'll admit I initially thought "how good can it be with barely any configuration?" — but running them in parallel completely changed my mind.

bash
# Install
pnpm add -D oxlint
 
# Run standalone
npx oxlint .
 
# Enable TypeScript type-aware linting
npx oxlint --tsconfig tsconfig.json .
jsonc
// package.json — hybrid strategy running in parallel with ESLint
{
  "scripts": {
    "lint": "oxlint . && eslint .",
    "lint:fast": "oxlint ."
  }
}

The hybrid strategy means disabling in ESLint the 700+ rules covered by Oxlint, and having ESLint handle only custom plugins or framework-specific rules. This lets you significantly reduce CI time without a full migration.

Using the JS plugin alpha, you can also use ESLint-compatible plugins directly in Oxlint.

javascript
// oxlint.config.js (alpha — released March 2026)
export default {
  plugins: ['./my-custom-plugin.js'],
  rules: {
    'my-plugin/my-rule': 'error',
  },
};

Configuring the Same Rule Across All Three Tools

Seeing the same "detect unused variables" rule configured in all three tools side by side makes the differences immediately apparent.

javascript
// ESLint — eslint.config.js
rules: {
  'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
}
json
// Biome — biome.json (rule names and structure differ)
{
  "linter": {
    "rules": {
      "correctness": {
        "noUnusedVariables": "error"
      }
    }
  }
}
json
// Oxlint — oxlint.json (uses the same ESLint-compatible rule names)
{
  "rules": {
    "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
  }
}

Biome uses different rule names from ESLint and groups them into categories like correctness and style. Oxlint keeps the same rule names as ESLint, lowering the migration barrier considerably. You really feel the difference when porting an existing ESLint configuration.


Pros and Cons Analysis

Tool-by-Tool Pros and Cons at a Glance

Tool Pros Cons Mitigation
ESLint Best-in-industry plugin ecosystem (React, Vue, TypeScript, Jest, Storybook, and more) Serious speed bottleneck on large projects (45–90 seconds for 100k LOC) Distribute load with Oxlint parallel execution
ESLint Most mature type-aware linting (@typescript-eslint) Frequent plugin flat config compatibility conflicts during v10 migration Verify each plugin's v10 support before upgrading
ESLint Flexible custom rule authoring with rich docs and community resources Formatting requires separate Prettier configuration —
Biome Manages linter + formatter in a single binary and config file No ESLint plugin ecosystem support (GritQL is in early stage) Unsuitable for projects requiring framework-specific rules
Biome 15–20× faster than ESLint, excellent LSP support for VS Code, JetBrains, and Zed (editor autocomplete and error integration) 10–20% of rules require manual handling when migrating existing projects Auto-convert with biome migrate eslint, then patch manually
Biome Runs immediately with biome check . — zero configuration required Some TypeScript type-aware rules have limitations —
Oxlint 50–100× faster than ESLint (100k LOC: 90 seconds → 2 seconds) JS plugin support is still in alpha Handle complex custom plugins with ESLint parallel execution
Oxlint Supports incremental migration strategy running in parallel with ESLint Standalone linter — formatting requires separate Oxfmt or Prettier —
Oxlint v1.0 stable, production-validated by Shopify, Airbnb, and Mercedes-Benz Dynamic oxlint.config.ts config file support incomplete Use static configuration for now

Common Mistakes in Practice

  1. Not checking plugin compatibility before upgrading to ESLint v10. Mismatched versions of eslint-config-next or @typescript-eslint in particular will cause installation failures outright. I once ran pnpm update without thinking and took down the entire CI. It's worth checking each plugin's GitHub issues or CHANGELOG for a v10 support announcement before upgrading.

  2. Pushing Biome directly into a project with many legacy plugins creates gaps. Biome can't replace plugins like eslint-plugin-react-hooks or eslint-plugin-jsx-a11y. When I actually added Biome to a team project, accessibility rule gaps appeared and we ended up compromising with a hybrid setup. It's more practical to try it first on new projects or projects with fewer plugin dependencies.

  3. Treating Oxlint as a "full ESLint replacement" will hit a wall sooner than expected. At this point, JS plugin support is still in alpha. The hybrid strategy — "add Oxlint on top of ESLint to speed things up" — is far more stable than "complete replacement."


Wrapping Up

As of 2026, the choices break down fairly clearly.

  • Heavy legacy plugin dependencies → ESLint (there's no alternative right now)
  • Starting a new project → Biome (zero configuration to get started, linter and formatter in one)
  • Existing team urgently needing faster CI → Oxlint parallel execution (shave off tens of seconds with just a few lines of config)

I've fully switched my personal side projects to Biome, and I'm experimenting with the Oxlint hybrid approach on team projects. CI time dropping from 60 seconds to 5 seconds alone made it more than worthwhile.

Three steps you can start with right now:

  1. Try adding Oxlint to your current project. Install with pnpm add -D oxlint and run npx oxlint . — you'll feel the speed difference immediately without touching your existing ESLint configuration.

  2. If your team needs to migrate to ESLint v10, use npx @eslint/config-inspector to visualize your current configuration, verify each plugin's flat config support status, and plan your upgrade order. With v9.x EOL (August 2026) approaching, starting now gives you plenty of time to transition comfortably.

  3. If you're starting a new project, create a Biome configuration with npx @biomejs/biome init and try handling all linting and formatting through a single biome.json — no .eslintrc or .prettierrc needed. Once you get used to it, you may find it hard to go back to managing two separate tools.


References

  • Biome vs ESLint vs Oxlint 2026: Which JS Linter to Pick | PkgPulse
  • OXC vs ESLint vs Biome: JavaScript Linting in 2026 | PkgPulse
  • Biome vs Oxlint in 2026: Which Rust-Powered Linter Should You Replace ESLint With | jsmanifest
  • Biome v2.5 — 500 Lint Rules, Plugin Code Fix, and Cross-File Linting | Biome Official Blog
  • Biome v2 — Biotype Official Announcement | Biome Official Blog
  • Biome Roadmap 2026 | Biome Official Blog
  • Announcing Oxlint 1.0 | VoidZero
  • Oxlint Benchmarks | oxc.rs Official
  • ESLint v10.0.0 Released | ESLint Official Blog
  • ESLint v9.0.0 Retrospective | ESLint Official Blog
  • Speed kills: It's time to retire ESLint and migrate to Oxlint | LogRocket
  • Migrating from ESLint, Biome, and Prettier to Oxlint and Oxfmt | Nicolas Charpentier
  • Biome: The ESLint and Prettier Killer? Migration Guide 2026 | DEV Community
  • TypeScript Linting and Formatting Tools Compared | sph.sh
  • Oxlint Alpha Adds ESLint Plugin Support | Prism News
  • Type-Aware Linting | Oxlint Official Docs
  • Biome vs ESLint: Comparing JavaScript Linters and Formatters | Better Stack
#ESLint#Biome#Oxlint#TypeScript#Rust#Prettier#FlatConfig#모노레포#GritQL#CI
Share

Table of Contents

Core ConceptsThe 2026 Linting Ecosystem LandscapeWhat ESLint v10 ChangedBiome — Linter and Formatter in OneOxlint — Optimized for SpeedPractical ApplicationExample 1: ESLint Flat Config — Existing Projects with Heavy Plugin DependenciesExample 2: Biome — New Project, Integrated Linter·Formatter SetupExample 3: Oxlint — Resolving CI Speed Bottlenecks, Parallel Execution Strategy with ESLintConfiguring the Same Rule Across All Three ToolsPros and Cons AnalysisTool-by-Tool Pros and Cons at a GlanceCommon Mistakes in PracticeWrapping UpReferences

Recommended Posts

Deleting 400 Lines of useMemo with React Compiler 1.0 Cut TBT by 15%
frontend

Deleting 400 Lines of useMemo with React Compiler 1.0 Cut TBT by 15%

What you need to know before handing memoization over to the compiler Honestly, when I first learned , I overused it myself. Thinking "shouldn't everything e...

June 26, 202618 min read
From Direct DB Calls Without RSC to Streaming — Where TanStack Start and Next.js Diverge on Full-Stack Boundaries
frontend

From Direct DB Calls Without RSC to Streaming — Where TanStack Start and Next.js Diverge on Full-Stack Boundaries

When Next.js 13 App Router launched, I'll be honest — it threw me off. The anxiety of toggling on and off wondering "is it just me?", the runtime errors from t...

June 26, 202627 min read
Vite 8 + Rolldown: How a Dual-Bundler Integration Cut Production Build Times by 87%
frontend

Vite 8 + Rolldown: How a Dual-Bundler Integration Cut Production Build Times by 87%

(Vite 8 Rolldown migration | build speed) When I first heard that a 46-second build had dropped to 6 seconds, I was honestly skeptical. Benchmark numbers alw...

June 26, 202617 min read
Removing Popper.js with CSS Anchor Positioning — Floating UI Handled Directly by the Browser
frontend

Removing Popper.js with CSS Anchor Positioning — Floating UI Handled Directly by the Browser

Anyone who has done frontend work eventually thinks to themselves: "Why is attaching a dropdown below a button this complicated?" When I first built my team's d...

June 26, 202621 min read
15 Frontend Accessibility Checklist Items After EAA Takes Effect (European Accessibility Act WCAG 2.1 AA Standard)
frontend

15 Frontend Accessibility Checklist Items After EAA Takes Effect (European Accessibility Act WCAG 2.1 AA Standard)

On June 28, 2025, I stopped while reading a message that came through on Slack: "EAA has taken effect — is our service okay?" At first I thought, "It's an EU la...

June 26, 202626 min read
Implementing Cloudflare Workers for Edge MFE Orchestration to Reduce TTFB
frontend

Implementing Cloudflare Workers for Edge MFE Orchestration to Reduce TTFB

Honestly, when I first heard about micro-frontends (MFE), my immediate thought was, "Independent deployments per team? Sounds great — but doesn't that mean the ...

June 23, 202623 min read