Skip to main content

Guide

Biome vs ESLint + Prettier 2026

Biome replaces ESLint + Prettier with a single Rust-based tool that runs 25x faster. In 2026, is it production-ready? Here's the real comparison for teams.

·PkgPulse Team·
0

TL;DR

Biome is production-ready for most JavaScript and TypeScript projects, but ESLint + Prettier is still the right call if you need the full ESLint plugin ecosystem. Biome's 25x speed advantage is real and meaningful in CI. The formatter is nearly identical to Prettier. The linter covers ~80% of common ESLint rules. What Biome can't replace yet: type-aware lint rules (requires TypeScript language service), framework-specific plugins (eslint-plugin-react-hooks, eslint-plugin-next), and any custom ESLint rules your team has written. Verdict: new projects → Biome. Existing projects with heavy plugin usage → evaluate the gap before switching.

Key Takeaways

  • Speed: Biome formats and lints in ~50ms; ESLint + Prettier takes ~2-3s for same project
  • Coverage: ~250 lint rules (growing); ESLint has 1000+ with the plugin ecosystem
  • Prettier compatibility: Biome's formatter matches Prettier output for ~96% of cases
  • Not yet in Biome: type-aware rules, React Hooks rules, Next.js plugin, custom rule authoring (in roadmap)
  • Configuration: one config file (biome.json) vs two separate configs

Speed: The Main Reason to Switch

# Real benchmark — a Next.js project with ~150 TypeScript files:

# ESLint + Prettier (separate runs):
npx eslint . --ext .ts,.tsx    → 3.2 seconds
npx prettier --check "**/*.{ts,tsx}"  → 1.1 seconds
Total: ~4.3 seconds

# Biome (lint + format together):
npx biome check .              → 0.18 seconds
# 24x faster combined

# CI impact (running on every PR):
# ESLint + Prettier: 4-5 seconds of your CI job
# Biome: ~0.2 seconds

# Pre-commit hooks (runs on staged files):
# ESLint + Prettier (lint-staged): ~1.5s per commit
# Biome: ~0.05s per commit
# Developer experience: the difference between "imperceptible" and "I notice this every time"

# Why Biome is faster:
# → Rust implementation (not Node.js)
# → Parallel processing of files
# → Single pass: lint + format in one traversal
# → No plugin loading overhead (rules are compiled in)

Setup: One Config vs Two Configs

// Biome — biome.json (one file for everything):
{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedVariables": "error"
      },
      "style": {
        "noParameterAssign": "warn"
      },
      "nursery": {
        "useSortedClasses": "warn"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double",
      "trailingCommas": "all",
      "semicolons": "always"
    }
  },
  "files": {
    "ignore": ["node_modules", "dist", ".next"]
  }
}

// Compare to ESLint + Prettier:
// .eslintrc.json OR eslint.config.mjs (ESLint 9 flat config)
// .prettierrc
// .prettierignore
// .eslintignore
// package.json scripts to run both
// lint-staged config for pre-commit hooks

// Biome replaces all of that with one file.

Lint Rules Coverage

Biome rules (v1.9, 2026) — grouped by ESLint equivalent:

✅ Covered well:
→ no-unused-vars, no-undef → biome: correctness/noUnusedVariables
→ no-console → biome: suspicious/noConsole
→ eqeqeq → biome: suspicious/noDoubleEquals
→ no-var → biome: style/noVar
→ prefer-const → biome: style/useConst
→ no-empty → biome: correctness/noEmptyBlockStatements
→ no-duplicate-imports → biome: correctness/noDuplicateObjectKeys
→ arrow-body-style → biome: style/useArrowFunction
→ object-shorthand → biome: style/useShorthandAssign
→ 200+ more rules...

⚠️  Partially covered / different API:
→ import/order → biome: organizeImports (reorders, doesn't configure)
→ jsx-a11y/* → basic accessibility rules, not all of jsx-a11y

❌ Not yet in Biome:
→ Type-aware rules (requires TypeScript type checker)
   → @typescript-eslint/no-floating-promises
   → @typescript-eslint/no-misused-promises
   → @typescript-eslint/consistent-return (typed)
→ eslint-plugin-react-hooks (useEffect deps, hooks rules)
→ eslint-plugin-next (app router patterns, image optimization rules)
→ eslint-plugin-import/no-cycle (circular dependency detection)
→ Custom rules your team wrote

The gap is real but smaller than it was.
Most "critical" lint rules are covered.
The missing ones are important for React/Next.js specifically.

Prettier Compatibility: How Close Is It?

// Biome's formatter is designed to match Prettier's output
// Real-world compatibility: ~96% identical for JS/TS

// Cases where they differ (edge cases):
// 1. Long template literals
const query = `SELECT * FROM users WHERE id = ${userId} AND status = 'active' AND created_at > '2024-01-01'`;
// Prettier: keeps on one line if fits, wraps differently
// Biome: similar but not identical on complex expressions

// 2. Decorators (TypeScript)
// Some class decorator formatting differs slightly

// 3. Complex JSX expressions
// Multi-line JSX attributes format slightly differently in edge cases

// For the vast majority of code: identical output.
// The 4% difference is in edge cases you'll rarely hit.

// Migration from Prettier:
# Run Biome formatter on your entire codebase once:
npx biome format --write .

# Check what changed (should be minimal):
git diff

# If there are many meaningful diffs, the code was inconsistently formatted.
# Biome will now be the source of truth.

# Teams commonly report:
# → 0-20 files changed on a typical codebase
# → Changes are whitespace/trailing comma in edge cases
# → No semantic code changes

Integration with Editors and CI

# VS Code — install the Biome extension:
# Extensions: "Biome" (biomejs.biome)
# settings.json:
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "[javascript]": { "editor.defaultFormatter": "biomejs.biome" },
  "[typescript]": { "editor.defaultFormatter": "biomejs.biome" },
  "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" }
}
# The extension works well — fast, accurate

# Pre-commit hooks (replace lint-staged + eslint/prettier):
# package.json:
{
  "scripts": {
    "prepare": "simple-git-hooks"
  },
  "simple-git-hooks": {
    "pre-commit": "npx lint-staged"
  },
  "lint-staged": {
    "*.{js,ts,jsx,tsx,json,css}": "biome check --apply --no-errors-on-unmatched"
  }
}

# CI (GitHub Actions):
- name: Lint and Format Check
  run: npx biome ci .
  # biome ci = check without --write; exits non-zero if issues found
  # Replaces separate eslint and prettier --check steps

# The biome ci command is designed exactly for this use case.

Migration from ESLint + Prettier

# Step 1: Initialize Biome
npm install --save-dev --save-exact @biomejs/biome

# Step 2: Generate config from existing ESLint config
npx biome migrate eslint --include-inspired
# --include-inspired: adds Biome rules "inspired by" your ESLint rules

# Step 3: Format with Biome once (commit this separately for clean history)
npx biome format --write .
git add -A && git commit -m "chore: migrate formatter to Biome"

# Step 4: Fix linting issues
npx biome check --apply .
# Some will be auto-fixed. Others need manual attention.

# Step 5: Find the ESLint rules you'll miss
# Go through your .eslintrc and categorize:
# → Rule covered by Biome? → Remove from ESLint
# → Rule is react-hooks or type-aware? → Keep ESLint for JUST those rules

# Step 6: The hybrid approach (if you need react-hooks rules):
# Keep ESLint for only what Biome doesn't cover:
# .eslintrc:
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
    // Nothing else — Biome handles the rest
  }
}

# This is the recommended migration path for React projects.
# Use Biome for formatting + most linting.
# Keep a minimal ESLint config for react-hooks only.

Verdict: Should You Switch?

New greenfield project (2026):
→ Yes — use Biome from day one
→ Add minimal ESLint config for react-hooks if it's a React project
→ The speed win in CI is real; the single config is a DX improvement
→ The rule gap doesn't matter if you're starting fresh

Existing project (small, no complex ESLint plugins):
→ Yes — migrate. 2-4 hour job. Net positive.
→ Use the migrate command; review diffs; ship it

Existing project (React/Next.js, heavy plugin usage):
→ Hybrid approach — Biome for format + most lint, ESLint for react-hooks + next
→ Not "switch" but "add Biome alongside a reduced ESLint config"
→ You still get the speed benefit for most of the work

Existing project (custom ESLint rules, type-aware rules critical):
→ Not yet — monitor Biome's type-aware rule roadmap
→ Expected in late 2026 based on their public roadmap
→ Reevaluate in 6 months

The trajectory is clear: Biome is getting better fast.
The rule gap that seemed large in 2024 is substantially smaller in 2026.
Type-aware rules are the final frontier.

Why Biome Is Fast: The Rust Architecture

Biome's speed advantage is architectural, not incidental. ESLint is a Node.js process that loads plugins as CommonJS modules, traverses the AST in JavaScript, and runs each lint rule as a JavaScript function. For a project with 150 TypeScript files and a moderate ESLint configuration (eslint-plugin-react, @typescript-eslint, eslint-plugin-import), the startup overhead alone — loading plugins, parsing configs, resolving extended configurations — consumes 400-600ms before a single file is analyzed. Each rule runs as a separate AST traversal pass in older ESLint versions, though ESLint 8+ improved this with rule-visitor merging.

Biome is compiled to native machine code. Its parser generates an AST in Rust, all lint rules are compiled visitor functions that run in a single traversal pass, and the formatter is a separate compiled pass. File I/O uses async I/O with Tokio, Rust's async runtime, and files are processed in parallel across CPU cores. The result is that a 150-file project that takes ESLint 3.2 seconds processes in 0.18 seconds with Biome — and this gap widens as projects grow, because ESLint's JavaScript overhead scales worse than Biome's native implementation.

Understanding Biome's Rule Coverage Gaps

The type-aware rules missing from Biome are not a random gap — they represent a fundamental architectural challenge. Type-aware rules like @typescript-eslint/no-floating-promises require access to the TypeScript type checker, which means they need to run TypeScript's language service. The TypeScript compiler is a large, complex JavaScript application that parses the entire program, resolves imports, and builds a type graph. Running this in a Rust process requires either spawning a separate Node.js process (adding overhead) or implementing a TypeScript type checker in Rust (an enormous undertaking).

Biome's team has indicated that type-aware rules are on their roadmap, with some approaching this via a Rust TypeScript parser (similar to what OXC is building). Until type-aware rules are available, projects that depend on no-floating-promises, no-misused-promises, or consistent-type-assertions need to keep a minimal ESLint configuration for those specific rules. The hybrid approach — Biome for formatting and most linting, a one-rule ESLint config for type-aware checks — works well in practice and still delivers most of the speed benefit.

Formatting Philosophy: Biome vs Prettier's Opinions

Prettier pioneered the idea of an "opinionated" formatter that eliminates formatting debates by making all decisions for you. Biome embraces this same philosophy, which means its formatter also makes strong opinions. The most consequential: Biome formats trailing commas, quote style, and semicolons according to your biome.json configuration, but the line-wrapping algorithm uses a print width and a Wadler-Lindig algorithm similar to Prettier's. The 96% compatibility figure means that in the vast majority of code, you cannot tell which formatter produced the output.

The 4% divergence clusters around edge cases: deeply nested JSX with complex attribute expressions, template literal strings that approach the line width limit, and some TypeScript generic expressions with multiple constraints. For a typical product codebase, you will see these differences in fewer than 20 files during migration. The practical approach is to run biome format --write ., commit the result as a dedicated formatting commit with [skip ci] or similar tagging, and establish Biome as the new formatting authority. Code review comments about formatting disappear because the formatter is always authoritative.

Ecosystem Compatibility and Configuration Composition

ESLint's flat config system (introduced in ESLint 9) improved configuration composability, but it also created a transitional period where many community plugins have not yet migrated from .eslintrc to flat config format. Running eslint.config.mjs with plugins still using the old format requires the FlatCompat utility, which adds complexity to the migration path. Biome sidesteps this entirely: it has no plugin ecosystem, so there is no compatibility problem. The tradeoff is that the 250 built-in rules are the complete set — you cannot add community rules written by third parties.

For most product codebases, 250 well-implemented rules covering correctness, style, and performance patterns is sufficient. The teams that find the rule gap most painful are those with established custom ESLint rules — rules that encode team-specific patterns, enforce internal API conventions, or check domain-specific error handling requirements. Biome currently has no mechanism for user-defined rules (though this is on the roadmap). Teams with substantial custom rule investments should plan a longer evaluation period before committing to Biome as a full replacement.

Adoption in the JavaScript Ecosystem

Biome's adoption trajectory in 2025-2026 reflects a pattern common to developer tools that offer dramatic speed improvements: initial adoption in performance-sensitive environments (CI optimization, large monorepos), followed by broader adoption as stability is demonstrated. The project reached v1.0 in September 2023 and has shipped multiple major versions since, adding rules, improving formatter compatibility, and hardening edge cases. By early 2026, teams managing 500k-line codebases are reporting successful full migrations, which is a meaningful signal for ecosystem maturity.

The Biome team has invested heavily in IDE tooling: the VS Code extension is actively maintained, the Language Server Protocol implementation passes most TypeScript-specific tests, and error messages are generally more actionable than ESLint's. One team reported that switching to Biome reduced their pre-commit hook time from 1.5 seconds to 50 milliseconds on a 200-file Next.js project — a change that meaningfully improves the commit experience for every engineer every day. When annoyances are eliminated from developer workflows, code quality tooling gets more consistent usage, which is the actual goal.

The Formatting Commitment Problem

One practical challenge in migrating from Prettier to Biome is the git blame pollution from a bulk formatting commit. In a repository with 5 years of history, a "migrate to Biome formatter" commit that touches 400 files makes git blame output on those files show the formatting commit rather than the original author for many lines. Teams that care about this can use .git-blame-ignore-revs — a file that lists commit SHAs that git blame should ignore — to restore meaningful blame output after a bulk formatting change. GitHub's web interface and many git clients respect this file when configured, making the transition less disruptive to code archaeology workflows.

Compare Biome, ESLint, and Prettier download trends at PkgPulse.

Compare Biome and ESLint package health on PkgPulse.

See also: AVA vs Jest and OXC vs ESLint vs Biome: JavaScript Linting in 2026, 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.