Skip to content

Is Biome Ready to Replace ESLint and Prettier?

A real benchmark of Biome against ESLint and Prettier on the same TypeScript monorepo, plus a rule-coverage check for what Biome still doesn't catch.

· · 6 min read
A 3D code brackets symbol on a soft gradient

Quick Take

I ran Biome against a 40-file TypeScript package that already had ESLint and Prettier configured, timed both, and diffed the output. The speed claim held up. The rule coverage gap was smaller than I expected, but it's still there.

Biome is a Rust-based linter and formatter that replaces both ESLint and Prettier with a single binary-backed tool, run through one config file instead of two. The speed claim around it gets repeated constantly, and it's true, but it's also not the interesting question for most teams. The interesting question is what breaks when you swap in a linter with a smaller rule set, and I wanted an actual answer instead of the marketing page's.

Quick take: Biome is a Rust-based linter and formatter that replaces both ESLint and Prettier in a single tool, and it's genuinely 10-25x faster on real codebases in practice. The trade-off is rule coverage: Biome's core and TypeScript rules cover most common cases, but plugin-specific rules, eslint-plugin-react-hooks in particular, don't have a direct equivalent yet. Check your config's rule list before migrating.

What Do the Benchmark Numbers Show?

I ran both toolchains against the same 40-file TypeScript package (roughly 6,000 lines), timing a full lint plus format pass, with cold caches for both:

ESLint 9 + PrettierBiome
Lint + format, cold4.8s0.31s
Lint + format, warm cache2.1s0.09s
Config files needed2 (eslint.config.js, .prettierrc)1 (biome.json)
Install size (node_modules)~180MB (ESLint + plugins + Prettier)~24MB (single binary-backed package)

The speed difference isn't subtle, and it compounds in CI, where a lint step running in 300ms instead of 5 seconds matters more once you multiply it across every PR check. The install size difference matters too, fewer dependencies means fewer version-compatibility issues over time, the exact problem the ESLint 9 flat config migration exists to work around. Cold cache is the benchmark condition where neither tool has run before on that machine, so it has to parse every file and rebuild its internal AST cache from nothing, which is the worst case most CI runners hit on every single job since they start from a fresh container. Biome's cold-cache number, 0.31 seconds against ESLint plus Prettier's 4.8 seconds, is roughly a 15x gap on this one 6,000-line package, and the warm-cache gap widens to about 23x, both comfortably inside the 10-25x range vendors advertise.

How Do You Set Up Biome on an Existing Project?

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

The migrate commands read your existing .eslintrc/eslint.config.js and .prettierrc, and generate a biome.json approximating the equivalent rules where a Biome equivalent exists. It's not a perfect 1:1 translation, expect to manually review the generated config, but it saves rewriting a rule list from scratch.

Here's the order that avoided surprises when I migrated the 40-file package:

  1. Run biome init first to get a baseline config before touching anything else.
  2. Run biome migrate eslint --write and diff the generated rule list against your original .eslintrc line by line.
  3. Run biome migrate prettier --write last, since formatting conflicts are the easiest to spot and the cheapest to fix if the migration gets something wrong.
// biome.json (generated + reviewed)
{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedVariables": "error"
      },
      "suspicious": {
        "noExplicitAny": "warn"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2
  }
}

Where Does the Rule Gap Actually Show Up?

Running the migrated config against the same 40 files, Biome caught the same issues ESLint did for roughly 90 percent of the violations I compared, formatting differences aside (both use an opinionated, mostly-compatible style). Rule coverage is the percentage of a given ESLint config's active rules that have a functionally equivalent Biome rule, and it's the number that matters far more than raw speed once a migration starts, because a fast linter that silently drops ten enforced rules just moved the risk somewhere less visible. Per the Biome docs, the linter ships hundreds of rules across correctness, suspicious, and style categories, which covers the ESLint core rule set almost completely, but coverage drops sharply once a project depends on framework-specific plugins rather than generic JavaScript or TypeScript rules. The gap, in order of how often it actually bit me:

  • eslint-plugin-react-hooks: rules-of-hooks and exhaustive-deps have no direct Biome equivalent as of this writing. If your codebase leans on exhaustive-deps catching a missing useEffect dependency, that specific safety net isn't there yet.
  • Highly custom, narrow plugins: a project-specific ESLint plugin enforcing an internal convention (a custom import-order rule, for instance) has no Biome path except keeping ESLint just for that one rule.
  • import/order and similar import-organization plugins: Biome has its own import-sorting feature (organizeImports), which covers the common case but isn't a rule-for-rule match with eslint-plugin-import's more configurable grouping options.

None of these are hard blockers, they're specific things to check against your actual config, not the marketing page's rule-count comparison.

Can You Use a Hybrid Approach: Biome for Formatting, ESLint for the Gap?

// package.json scripts
{
  "scripts": {
    "format": "biome format --write .",
    "lint:biome": "biome lint .",
    "lint:hooks": "eslint . --rulesdir ./node_modules/eslint-plugin-react-hooks --rule '{\"react-hooks/exhaustive-deps\":\"error\"}'"
  }
}

A hybrid setup like this isn't the cleanest long-term architecture, but it's a reasonable migration path: get the formatting speed win immediately (Biome replacing Prettier is close to risk-free), get most of the linting speed win, and keep a narrow ESLint invocation only for the specific rules Biome doesn't cover yet, rather than blocking the whole migration on 100 percent rule parity. In practice this hybrid setup lasted about six weeks on the package I migrated before the team decided the remaining ESLint invocation, just two rules from eslint-plugin-react-hooks, was cheap enough to keep permanently rather than chase a full cutover, which according to the migration guide is a perfectly normal end state, not a stalled migration.

Conclusion

Biome's speed numbers are real and reproducible, this isn't a case of a benchmark that doesn't hold up under a second look. The actual decision point is narrower than "is Biome fast," it's "does my specific rule set have Biome equivalents," and eslint-plugin-react-hooks is the most likely place a React codebase finds a gap. Run the migration tool, diff the generated config against your original rule list, and decide from there rather than from the speed claim alone.

One more thing in Biome's favour that never shows up in a speed table: a linter and formatter lives at the build boundary, so its exit cost is close to nothing. Config files change, source files don't. That's the opposite of a runtime dependency whose API ends up in 900 call sites, and it's why switching linters is a Tuesday afternoon rather than a quarter.

Frequently Asked Questions

Is Biome faster than ESLint and Prettier?
Yes, substantially, because Biome is written in Rust and runs both linting and formatting in a single process, versus two separate JavaScript-based tools each with their own file-parsing pass. On real-world codebases, teams commonly report 10-25x faster linting and formatting combined compared to ESLint plus Prettier run separately, though the exact multiplier depends on codebase size and how many custom ESLint plugins were previously in the pipeline.
Does Biome support all the same rules as ESLint?
No, and this is the real trade-off, not the speed. Biome's rule set covers the vast majority of common ESLint core rules and typescript-eslint's most-used rules, but plugin ecosystems like eslint-plugin-react-hooks (rules of hooks) or highly specific custom rules from smaller plugins don't have a Biome equivalent yet. Check your specific ESLint config's rule list against Biome's rule reference before migrating a codebase with heavy plugin usage.
Can I use Biome for formatting only and keep ESLint for linting?
Yes, and it's a common incremental adoption path. Biome's formatter alone is a drop-in Prettier replacement for most codebases, faster and with fewer configuration options by design (a deliberate opinionated choice, similar to Prettier's own philosophy). Running Biome for formatting and keeping ESLint for the rules Biome doesn't yet cover gets you most of the speed win without a full linter migration.