Skip to main content

Biome vs ESLint + Prettier: Is the All-in-One Linter Ready?

·PkgPulse Team

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.

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

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.