50% off SaaS Starter Kit — only for the first 100 buildersGrab it →
← Back to blog
best-practicesMay 26, 2026·7 min read

Biome vs ESLint + Prettier — Is It Time to Switch?

Biome promises to replace ESLint and Prettier with one fast Rust-based tool. We ran both on a real Next.js project. Here's what we found.

Ștefan Binisor

Ștefan Binisor

Co-founder, peal.dev

Biome vs ESLint + Prettier — Is It Time to Switch?

Every few months someone posts "just switched to Biome, deleted 400 lines of config" and the replies are half people excited and half people asking "but does it do X?" We finally sat down and actually ran both setups on a real Next.js project — same codebase, same rules, measured the differences. Here's the honest breakdown.

What Biome Actually Is

Biome is a Rust-based toolchain that does formatting (what Prettier does), linting (what ESLint does), and import sorting — all in one binary, no plugins, no peer dependencies, no `node_modules` rabbit hole. It came out of the Rome project, which had a rocky start, but Biome forked off, shipped consistently, and hit 1.0 in late 2023. By 2025 it's genuinely production-ready.

The pitch is simple: install one package, add one config file, and you're done. No more `eslint-config-prettier` to disable ESLint rules that conflict with Prettier. No more `@typescript-eslint/parser` and wondering why your lint takes 8 seconds. Just fast, deterministic, opinionated tooling.

The ESLint + Prettier Setup You're Probably Running

Let's be real — most Next.js projects end up with a config that looks like this after six months of 'just add this package' decisions:

// package.json devDependencies (the usual suspects)
{
  "@typescript-eslint/eslint-plugin": "^7.0.0",
  "@typescript-eslint/parser": "^7.0.0",
  "eslint": "^8.57.0",
  "eslint-config-next": "14.2.0",
  "eslint-config-prettier": "^9.1.0",
  "eslint-plugin-import": "^2.29.1",
  "eslint-plugin-jsx-a11y": "^6.8.0",
  "eslint-plugin-react": "^7.34.1",
  "eslint-plugin-react-hooks": "^4.6.2",
  "prettier": "^3.2.5",
  "prettier-plugin-tailwindcss": "^0.5.11"
}

That's 10 packages just to lint and format. They all need to agree on versions, they can conflict with each other, and upgrading ESLint from v8 to v9 means rewriting your flat config. We've wasted real hours debugging why `eslint-config-prettier` wasn't suppressing a specific rule, or why `@typescript-eslint/parser` was choking on a file. It's not a disaster — it works — but it's a lot of moving parts.

The Biome Setup

Here's the full equivalent setup with Biome:

pnpm add -D @biomejs/biome
npx @biomejs/biome init
// biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedVariables": "error",
        "noUnusedImports": "error"
      },
      "suspicious": {
        "noExplicitAny": "warn"
      },
      "style": {
        "noNonNullAssertion": "warn"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double",
      "trailingCommas": "es5",
      "semicolons": "always"
    }
  },
  "files": {
    "ignore": [".next", "node_modules", "dist"]
  }
}

One package. One config file. That's it. And it runs in milliseconds — we're talking 0.3s on a 200-file project where ESLint takes 6-8s. That speed difference is noticeable in pre-commit hooks, where ESLint sometimes makes you regret adding it to `lint-staged` at all.

Where Biome Falls Short

Here's where we have to be honest, because the Biome hype sometimes glosses over the real gaps.

  • No eslint-config-next equivalent. Next.js ships rules specific to the framework — things like no-html-link-for-pages, no-sync-scripts, missing key props in dynamic routes. Biome doesn't have these. You'd catch most of them with TypeScript and general rules, but not all.
  • No Tailwind class sorting. prettier-plugin-tailwindcss is beloved. Biome doesn't sort Tailwind classes yet (it's on the roadmap but not there as of 2025). If your team is religious about class order, this is a real blocker.
  • Fewer rules overall. ESLint has thousands of community rules. Biome has a few hundred well-chosen ones. For most projects that's fine, but if you rely on niche plugins like eslint-plugin-security or custom org rules, you're stuck.
  • No custom rule plugins. You can't write your own Biome rules (yet). If you have project-specific lint rules baked into an internal ESLint plugin, that's not portable.
  • ESLint v9 flat config is actually decent now. The new config format is verbose but it's a lot more composable. If you're already on it, the migration cost back to Biome isn't zero.
The question isn't "is Biome better?" — it's "does Biome cover enough of what you actually use?" For a lot of projects, the answer is yes. For Next.js specifically, it's more nuanced.

The Hybrid Approach (What We Actually Do)

After testing both, we landed on a hybrid that gives us most of Biome's speed while keeping the Next.js-specific rules. Biome handles formatting and most linting. ESLint handles only what Biome can't — the Next.js rules.

// .eslintrc.json — stripped to only what Biome doesn't cover
{
  "extends": ["next/core-web-vitals"],
  "rules": {
    // Let Biome handle everything else.
    // Only keeping Next.js-specific rules here.
    "@next/next/no-html-link-for-pages": "error",
    "@next/next/no-sync-scripts": "error"
  }
}
// package.json scripts
{
  "scripts": {
    "lint": "biome check . && next lint",
    "lint:fix": "biome check --write . && next lint --fix",
    "format": "biome format --write ."
  }
}

Is running two tools ideal? No. But Biome runs in 0.3s, and then ESLint only checks Next.js rules which is much faster than a full ESLint pass. Total lint time went from ~8s to ~2s on our projects. We'll take it.

Setting Up the Pre-commit Hook

This is where Biome's speed actually shines. Pre-commit hooks with slow ESLint are the reason developers do `git commit --no-verify` and then regret it at 2am. With Biome, the hook is fast enough that people actually keep it enabled.

pnpm add -D lint-staged husky
npx husky init
// package.json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx,json,css}": [
      "biome check --write --no-errors-on-unmatched"
    ],
    "*.{ts,tsx}": [
      "bash -c 'next lint --fix'"
    ]
  }
}
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm lint-staged

The `--no-errors-on-unmatched` flag prevents Biome from throwing an error when lint-staged passes it a file type it doesn't handle (like a `.md` file). Learned that one from a confused error message at 11pm.

Migrating an Existing Project

Biome ships with a migration command that's actually useful:

# Run this from your project root — it reads your .eslintrc and .prettierrc
# and generates a biome.json with equivalent config
npx @biomejs/biome migrate eslint --write
npx @biomejs/biome migrate prettier --write

It won't get everything — custom plugins, extends chains, and conditional configs don't always translate. But it gets you 80% there automatically. After running it, do a `biome check .` and triage whatever comes up. In our experience, most of it is stylistic stuff that you can either fix or mark as allowed.

One gotcha: Biome is stricter about some things by default. It'll flag `console.log` as a warning, complain about `noParameterAssign`, and be opinionated about double vs single quotes. Go through the rules list and consciously decide what you want — don't just disable everything that shows red.

The Verdict: Should You Switch?

It depends on what you're building and how much you rely on the ESLint ecosystem. Here's how we think about it:

  • New project, no Tailwind class sorting requirement: Go full Biome. Add eslint-config-next for the framework rules and you're done. The setup is 20 minutes and you'll never think about it again.
  • New project with heavy Tailwind usage: Hybrid setup. Biome for everything, Prettier just for Tailwind class sorting with prettier-plugin-tailwindcss.
  • Existing project on ESLint v8: Consider migrating. The migration command does most of the work and you escape the v9 upgrade anyway.
  • Existing project with custom ESLint plugins or org-wide rules: Stay on ESLint for now. Biome can't replace rules you've written yourself.
  • You care about pre-commit hook speed: This alone might be worth switching. 0.3s vs 8s is not a small difference when you're committing 20 times a day.

The templates on peal.dev ship with the hybrid approach — Biome for formatting and core linting, Next.js ESLint for framework rules. It means new projects start with sensible defaults without the overhead of configuring 10 packages, and the pre-commit hook actually stays enabled because it's fast enough to not be annoying.

The ESLint ecosystem isn't going anywhere — it's too embedded and too extensible. But for the 80% of projects that don't need custom rules or niche plugins, Biome is just less friction. Less config, fewer dependencies, faster feedback. That's worth a lot.

The linting landscape in 2025 isn't "Biome replaced ESLint" — it's "Biome is good enough for most things and excellent for formatting." The upgrade path from Prettier to Biome's formatter is basically zero-friction. The upgrade from ESLint is more nuanced. Do it project by project, and keep the Next.js rules around either way — they catch real mistakes.

Newsletter

Liked this post? There's more where it came from.

Dev guides, honest build stories, and the occasional 2am debugging confession — straight to your inbox. No spam, unsubscribe anytime.

Browse templates
Written by humansWeekly dropsSubscriber perks

Join the Discord

Ask questions, share builds, get help from founders