Skip to main content

Guide

OXC vs ESLint vs Biome: JavaScript Linting in 2026

The JavaScript linting landscape has fragmented. OXC brings Rust-speed parsing to the ecosystem. Biome is the all-in-one formatter+linter. ESLint v9 has flat.

·PkgPulse Team·
0

TL;DR

The linting space is in flux in 2026. ESLint v9 with flat config is the universal default — 50M+ weekly downloads, every framework supports it. Biome is the fastest all-in-one linter+formatter at near-zero config, and a compelling choice for greenfield projects. OXC (oxlint) is the newest entrant — a Rust-based linter that's 50-100x faster than ESLint — but lacks the rule ecosystem. Most teams should use ESLint v9 or Biome; OXC is worth watching but not production-primary yet.

Key Takeaways

  • ESLint v9 introduced flat config (eslint.config.js) — the old .eslintrc format is officially deprecated
  • Biome v1.9+ handles formatting AND linting in one tool (replaces ESLint + Prettier)
  • OXC (oxlint) is 50-100x faster than ESLint but covers ~300 rules vs ESLint's 700+
  • Biome has ~200 lint rules (growing), zero JavaScript, written in Rust
  • TypeScript support: Biome has first-class TS support; OXC has TS parsing; ESLint needs @typescript-eslint
  • Ecosystem: ESLint has 4000+ plugins; Biome is standalone; OXC ecosystem is nascent

ESLint v9: Flat Config Migration

ESLint crossed 50M weekly downloads in 2026. The biggest change in v9 is the flat config system.eslintrc.* is gone, replaced by eslint.config.js:

Old Way (.eslintrc.json — deprecated)

{
  "env": { "browser": true, "es2022": true },
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "rules": {
    "no-console": "warn",
    "@typescript-eslint/no-unused-vars": "error"
  }
}

New Way (eslint.config.js — v9 default)

// eslint.config.js
import globals from "globals";
import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default [
  // Built-in recommended rules
  js.configs.recommended,

  // TypeScript support
  ...tseslint.configs.recommended,

  // Global settings
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
      parserOptions: {
        project: "./tsconfig.json",
      },
    },
  },

  // Your custom rules
  {
    rules: {
      "no-console": "warn",
      "@typescript-eslint/no-explicit-any": "error",
      "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
    },
  },

  // Ignores (replaces .eslintignore)
  {
    ignores: ["dist/**", "node_modules/**", "coverage/**", "*.config.js"],
  },
];

ESLint v9 with React

// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";

export default tseslint.config(
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
    },
  }
);

ESLint Performance (2026 Benchmarks)

ESLint is JavaScript, not Rust. For large codebases, it's slow:

Codebase sizeESLint v9BiomeOXC
100 files1.2s0.08s0.03s
500 files5.8s0.3s0.1s
2000 files24s1.1s0.4s

ESLint supports --cache to skip unchanged files, but cold runs are slow.


Biome: The All-In-One Alternative

Biome was born from Rome Tools' pivot — it's a Rust-based formatter + linter + import sorter that replaces ESLint + Prettier in a single binary.

npm install --save-dev @biomejs/biome
npx @biomejs/biome init

biome.json

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "organizeImports": { "enabled": true },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUndeclaredVariables": "error",
        "useExhaustiveDependencies": "error"
      },
      "suspicious": {
        "noExplicitAny": "warn",
        "noDoubleEquals": "error"
      },
      "style": {
        "useConst": "error",
        "noNonNullAssertion": "warn"
      }
    }
  },
  "javascript": {
    "parser": {
      "unsafeParameterDecoratorsEnabled": false
    },
    "formatter": {
      "quoteStyle": "double",
      "trailingCommas": "es5"
    }
  }
}

Biome CLI

# Lint and format together
biome check --write .

# Just lint (no formatting)
biome lint .

# Just format
biome format --write .

# CI mode (no changes, just error on violations)
biome ci .

Biome's Rule Coverage

Biome v1.9 ships ~200 lint rules across:

  • correctness — logic errors, exhaustive deps, unused variables
  • suspicious — double equals, commented code, console.log
  • style — const vs let, arrow functions, template literals
  • nursery — experimental rules (opt-in)
  • performance — no-barrel-files, no-array-index-key

What Biome doesn't have: Biome has no plugin system. If you need custom rules, Biome can't do it. ESLint's plugin ecosystem (4000+ packages) is irreplaceable for complex projects.

Biome vs Prettier: Compatibility

Biome formats identically to Prettier for ~97% of cases. The remaining 3% are edge cases where Biome made different aesthetic choices. The biome migrate prettier command handles the gap:

npx @biomejs/biome migrate prettier --write
# Reads .prettierrc and applies those settings to biome.json

OXC: Rust-Speed Linting

OXC (JavaScript Oxidation Compiler) is the Rust-based JavaScript/TypeScript toolchain that includes:

  • oxc_parser — the fastest JS/TS parser in the ecosystem
  • oxlint — linter built on oxc_parser (the installable npm package)
  • oxc_transformer — TS/JSX transformer (Babel replacement)
  • rolldown uses oxc_parser internally
npm install oxlint --save-dev
npx oxlint .

OXC Speed

OXC's headline claim is 50-100x faster than ESLint. From their benchmarks:

CommandESLintOXC
Lint TypeScript (cold)24.0s (2000 files)0.4s
Lint React codebase9.2s (800 files)0.18s

This is achieved by being written in Rust and using multithreaded parsing.

OXC Configuration

// .oxlintrc.json
{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "rules": {
    "no-unused-vars": "error",
    "no-console": "warn",
    "eqeqeq": "error",
    "prefer-const": "error"
  },
  "plugins": ["react", "typescript"],
  "env": {
    "browser": true,
    "node": true
  },
  "ignorePatterns": ["dist/**", "node_modules/**"]
}

OXC Rule Coverage

OXC supports ~300 rules including ESLint core rules, React hooks rules, and TypeScript rules. The coverage is growing rapidly, but there are gaps:

OXC has:

  • ✅ All ESLint core recommended rules
  • ✅ React hooks rules (react-hooks/rules-of-hooks, react-hooks/exhaustive-deps)
  • ✅ Common TypeScript rules
  • ✅ Import/no-cycle (graph-based, very fast)

OXC doesn't have:

  • @typescript-eslint advanced type-aware rules (requires type checking)
  • ❌ Custom plugin system
  • ❌ Most framework-specific plugins (Svelte, Vue, Astro)
  • eslint-plugin-security, eslint-plugin-sonarjs

OXC as a Speed Layer

A popular pattern is running OXC first (fast, catches obvious issues) and ESLint only for type-aware rules:

// package.json
{
  "scripts": {
    "lint": "oxlint . && eslint --cache . --rule '@typescript-eslint/no-floating-promises: error'",
    "lint:fast": "oxlint .",
    "lint:types": "eslint --no-eslintrc -c eslint.types.config.js ."
  }
}

Framework-Specific Recommendations

Next.js Project

// eslint.config.js — Next.js uses ESLint by default
import nextPlugin from "@next/eslint-plugin-next";

export default [
  ...tseslint.configs.recommended,
  {
    plugins: { "@next/next": nextPlugin },
    rules: {
      ...nextPlugin.configs.recommended.rules,
      ...nextPlugin.configs["core-web-vitals"].rules,
    },
  },
];

Next.js's eslint-config-next has not been ported to Biome. Use ESLint for Next.js projects.

Vite + React (Biome is a great fit)

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install --save-dev @biomejs/biome
npx @biomejs/biome init
# Remove eslint and prettier from devDependencies

Biome replaces ESLint + Prettier perfectly for Vite/React apps without framework-specific lint rules.

Library Development

Use ESLint. Libraries often need:

  • @typescript-eslint type-aware rules
  • eslint-plugin-jest or eslint-plugin-vitest
  • eslint-plugin-jsdoc for API documentation
  • Custom rules for your library's API patterns

Feature Comparison Table

FeatureESLint v9BiomeOXC
Speed⚠️ Slow (JS)✅ Fast (Rust)✅ Fastest (Rust)
Formatting❌ (needs Prettier)✅ Built-in
Import sorting🔌 Plugin needed✅ Built-in
Rule count✅ 700+ core✅ ~200⚠️ ~300
Plugin ecosystem✅ 4000+ plugins❌ None❌ None
Type-aware rules✅ Full TS support⚠️ Limited
Custom rules✅ Full JS API
Vue/Svelte/Astro✅ Via plugins⚠️ Partial
Config complexity⚠️ Moderate✅ Minimal✅ Minimal
Auto-fix
Editor integration✅ Universal✅ VSCode/JetBrains⚠️ Growing
Weekly downloads50M+3M+Growing

Migration Paths

.eslintrc → ESLint v9 Flat Config

# Official migration helper
npx @eslint/migrate-config .eslintrc.json

This generates an eslint.config.js from your old config. Usually requires manual cleanup.

ESLint + Prettier → Biome

npx @biomejs/biome migrate eslint --write
npx @biomejs/biome migrate prettier --write

Two commands convert your existing configs. Then uninstall ESLint and Prettier.


ProjectRecommendationWhy
Next.js appESLint v9 + next pluginNext.js requires ESLint; flat config is clean
Vite + ReactBiomeNo framework-specific rules needed; Biome is faster
Node.js libraryESLint v9Type-aware rules critical for library quality
MonorepoOXC (fast CI) + ESLint (type rules)Best of both worlds
New SvelteKit projectESLint + svelte pluginBiome/OXC don't support Svelte syntax yet
Legacy Webpack projectESLint v9 (migrate to flat config)Stability over speed

The Two-Pass Linting Strategy

Running OXC before ESLint has become a productive pattern in 2026 for teams that need speed without abandoning the ESLint ecosystem. The idea is simple: OXC handles the fast, obvious checks — unused variables, no-console violations, eqeqeq, import cycles — in milliseconds, failing the CI pipeline immediately on common mistakes before invoking ESLint's slower type-aware analysis. Developers with simple errors never wait for ESLint's full pass.

The split works because OXC and ESLint address different rule categories. OXC runs as a pre-check covering 80% of errors at roughly 1% of the time cost. ESLint then handles the rules that require TypeScript's type information: no-floating-promises, strict-boolean-expressions, no-unsafe-call, no-unsafe-member-access — rules that OXC cannot implement without invoking the type checker. The pipeline exits on the first failure, so if OXC catches a no-console violation or an import cycle, ESLint never runs at all.

For teams switching to this pattern: configure ESLint to run only type-aware rules (@typescript-eslint/strict-type-checked subset), and let OXC handle everything else. Remove any ESLint rules that OXC already covers — running both for the same rule wastes time and can produce inconsistent messages. The configuration overhead is worth it in codebases over 200 files, where ESLint's cold run crosses the 10-second threshold but OXC finishes in under half a second.

// package.json — two-pass linting
{
  "scripts": {
    "lint": "oxlint . && eslint --cache -c eslint.types.config.js .",
    "lint:fast": "oxlint .",
    "lint:ci": "oxlint . && eslint --max-warnings 0 -c eslint.types.config.js ."
  }
}

Biome's Plugin Gap and Formatting Opinion

Biome formats identically to Prettier for roughly 97% of code patterns. The remaining 3% shows up in specific edge cases: template literal expressions with complex embedded logic, certain JSX attribute arrangements, and long function signatures. For teams migrating from Prettier, the biome migrate prettier command handles the transition cleanly, and the remaining output differences are minor enough to accept. The practical risk in migration is not format differences — it's discovering that a framework-specific ESLint plugin your codebase relies on has no Biome equivalent.

ESLint's plugin ecosystem is irreplaceable for specialized use cases. eslint-plugin-jsx-a11y enforces accessibility rules that Biome doesn't cover. eslint-plugin-security flags patterns like exec() calls with user input. eslint-plugin-n enforces Node.js API usage constraints. eslint-plugin-vue handles Vue single-file components. None of these exist in Biome, and Biome has no plugin API for implementing them — the design is intentional. Biome's development team maintains all rules internally to guarantee performance and consistency.

The formatting opinion gap is a subtler concern. Prettier's non-configurability is part of its value: there's no point debating formatting because Prettier makes the choice for you. Biome offers some configuration (quoteStyle, trailingCommas, lineWidth, indentStyle), which means teams can end up with inconsistent Biome configurations across projects, reintroducing the formatting discussions that Prettier adoption was meant to end. The recommended approach for teams adopting Biome: treat the default Biome configuration as a standard and resist per-project customization unless a specific requirement forces it.

The result in 2026: Biome is the right choice for TypeScript/React projects without specialized rule requirements that can benefit from a single-tool workflow. ESLint remains the right choice wherever framework-specific plugins, accessibility enforcement, or custom rules are non-negotiable.


Methodology

  • Benchmarked on a 2000-file TypeScript React codebase (MacBook M3 Pro + GitHub Actions ubuntu-latest)
  • Tested ESLint v9.18, Biome v1.9.3, OXC 0.14.x
  • Reviewed OXC roadmap for rule implementation status
  • Tested all flat config migration paths with official migration tools
  • Analyzed npm download trends for eslint, @biomejs/biome, and oxlint packages

See ESLint vs Biome download trends on PkgPulse — updated in real time.

See also: ESLint vs Prettier and AVA vs Jest, Biome vs ESLint + Prettier: The Linter Wars 2026.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.