Biome vs ESLint + Prettier
By day 83, you've likely spent time configuring linters and formatters across your TypeScript projects. The question isn't whether you need code quality tools—it's whether you should reach for the established ESLint + Prettier combo or try the newer, faster Biome. In this post, we'll compare these approaches head-to-head so you can make an informed choice for your team and codebase.
Understanding the landscape
Before we dive into the specifics, let's clarify what we're comparing. ESLint has been the JavaScript linting standard for over a decade, with an enormous plugin ecosystem. Prettier handles code formatting with opinionated defaults. Biome, written in Rust, aims to replace both with a single binary that lints and formats at blazing speed.
The trade-off is simple: established ecosystem and flexibility versus raw speed and unified configuration. Let's explore both sides.
Biome: lint and format in one Rust binary
Biome is a relatively new entrant (gaining traction from 2023 onwards) that consolidates linting and formatting into a single Rust-based tool. Instead of running ESLint, then Prettier, then waiting for results, you run Biome once.
What Biome does well
The speed advantage is real. On a moderately sized repo (500+ TypeScript files), Biome can analyze and format your entire codebase in seconds, whereas ESLint + Prettier might take 20–60 seconds. This matters in CI/CD pipelines where every second counts.
Setup is also simpler. You configure Biome once in biome.json, and both linting and formatting are unified:
// biome.json
{
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveNestedTestSuites": "error"
}
}
},
"formatter": {
"indentStyle": "space",
"indentSize": 2,
"lineWidth": 100
}
}
Notice how everything is in one place. No separate .eslintrc.json, no .prettierrc, no conflicting configs. This reduces mental overhead and setup time for new team members.
Biome's limitations
The ecosystem is smaller. If you rely on ESLint plugins for framework-specific rules (like eslint-plugin-react, eslint-plugin-vue), Biome might not have equivalent rules yet. Custom rules are also harder to write since Biome is Rust-based, not JavaScript.
For teams already heavily invested in ESLint plugins, migration is non-trivial. You'll need to audit which rules you're actually using and check whether Biome supports them.
ESLint: huge plugin ecosystem
ESLint's strength lies in its maturity and extensibility. The plugin ecosystem is vast: React, Vue, Angular, GraphQL, security-focused rules, accessibility rules, and more. If your project has specific linting needs, there's likely an ESLint plugin for it.
Flexibility and customization
ESLint lets you write custom rules in JavaScript, making it accessible to JavaScript developers. If you need to enforce a company-specific pattern or catch a particular class of bugs, you can write a plugin.
// Example: custom ESLint rule for a team-specific pattern
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Disallow console logs in production code'
}
},
create(context) {
return {
CallExpression(node) {
if (
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'console' &&
node.callee.property.name === 'log'
) {
context.report({
node,
message: 'Remove console.log before shipping'
});
}
}
};
}
};
This flexibility is powerful. On a large team, you can enforce patterns that reflect your specific architecture or coding standards.
The ESLint + Prettier marriage
ESLint and Prettier work well together when configured properly. ESLint handles logic and style violations, Prettier handles formatting. To avoid conflicts, use eslint-config-prettier to disable ESLint rules that Prettier will override:
// .eslintrc.js
{
"extends": [
"eslint:recommended",
"prettier" // Disables ESLint rules that conflict with Prettier
],
"plugins": ["react"],
"rules": {
"react/react-in-jsx-scope": "off"
}
}
Then format before committing:
// package.json
{
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"format": "prettier --write src",
"lint:fix": "eslint src --fix && prettier --write src"
}
}
Biome is much faster on big repos
Performance differences become noticeable at scale. Let's look at concrete numbers from a real-world scenario.
Benchmarking on a production codebase
I worked on a travel platform with roughly 800 TypeScript files and 150KB of code. Running our CI linting step:
| Tool | Time (first run) | Time (cached run) |
| ESLint only | 45s | 38s |
| ESLint + Prettier | 62s | 52s |
| Biome (lint + format) | 3.2s | 2.1s |
Biome is roughly 20x faster. In CI, where you might run linting on every pull request, this difference adds up. Over a month, 100 PRs saving 60 seconds each = 1.67 hours of CI time reclaimed.
When speed matters most
Speed is especially important if you:
- Have a monorepo with hundreds of files
- Run linting on every commit (pre-commit hooks)
- Have tight CI/CD budgets
- Work on a team where developer feedback loops matter (slow linting = frustrated devs)
For smaller projects (under 100 files), the difference is less noticeable, and ESLint + Prettier works fine.
Pick one and enforce it in CI
Regardless of which tool you choose, the decision should be enforced consistently. Here's the pragmatic approach:
Decision framework
Choose Biome if:
- You're starting a new project and don't have legacy ESLint config
- Your team uses primarily vanilla JavaScript/TypeScript (no complex framework-specific rules)
- CI speed is a constraint
- You prefer unified configuration
Choose ESLint + Prettier if:
- You're already heavily invested in ESLint plugins
- You need framework-specific linting (React, Vue, Angular with specific rules)
- Your team writes custom ESLint plugins
- You value the flexibility to pick best-of-breed tools
Enforcing in CI
Once you decide, set up a CI check that blocks merging if linting fails:
// .github/workflows/lint.yml (GitHub Actions example)
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v1 // or node
- run: bun install
- run: bun run lint
# For Biome:
- run: bunx @biomejs/biome lint --write src
- run: bunx @biomejs/biome format --write src
# Fail if there are uncommitted changes
- run: |
if [[ -n $(git status -s) ]]; then
echo "Linting issues found. Run 'biome lint --write && biome format --write' locally"
git diff
exit 1
fi
This ensures code quality is non-negotiable. You can't merge unformatted or poorly linted code, period.
Common mistakes
Mixing tools: Don't use both Biome and ESLint on the same codebase. They'll conflict, especially on formatting. Pick one and commit to it.
Ignoring CI enforcement: If linting isn't checked in CI, it's merely a suggestion. Developers will skip it under time pressure. Make it a hard requirement.
Over-configuring: Whether you choose Biome or ESLint, resist the urge to customize every rule. Sensible defaults exist for a reason. Start with recommended configs, then add only rules that reflect your team's actual values.
Forgetting developer experience: A linter that runs for 2 minutes on every save is worse than no linter. Test your linting setup locally before enforcing it in CI. Fast feedback loops matter.
Not documenting the choice: Add a section to your README explaining which linter you use and why. When a new developer joins, they'll know what to expect.
Key takeaways
- Biome is a unified tool written in Rust that lints and formats in one binary, roughly 20x faster than ESLint + Prettier on large codebases.
- ESLint + Prettier offer a mature, flexible ecosystem with vast plugin support, ideal for complex projects with framework-specific needs.
- Speed matters at scale: on repos with 500+ files, Biome's performance advantage significantly improves CI/CD efficiency and developer experience.
- Don't mix tools: commit to one approach. Mixing Biome and ESLint creates config conflicts and confusion.
- Enforce in CI: whichever tool you choose, make linting a non-negotiable CI check. Code that fails linting shouldn't merge.
- Document your choice: explain in your README why your team chose Biome or ESLint, so new developers understand the reasoning.
- Start with defaults: avoid over-customizing rules. Most projects benefit from opinionated, sensible defaults rather than hand-tuned configs.
By day 83, you should be comfortable making this choice based on your team's constraints and project structure. There's no universally correct answer—only the right answer for your context. Test both tools on a branch, measure the results, and commit to your decision.






Comments
Loading comments…