Our ESLint setup had 4 config files, 23 plugins, and took 12 seconds to lint the mobile app. Biome does it in under a second with zero plugins. This is the story of a migration that was boring in the best possible way.
The ESLint Tax
Every JavaScript developer has been here: you set up ESLint, it works great, then six months later you're maintaining a .eslintrc.js that's longer than some of your components. Plugins for React, React Native, TypeScript, import sorting, accessibility — each one adding its own configuration surface and its own opinions about whether your useEffect dependency array is complete.
In a monorepo, this multiplies. We had:
apps/mobile/.eslintrc.js— React Native rulesapps/web/.eslintrc.js— Next.js rulespackages/backend/.eslintrc.js— Convex/Node rules- Root
.eslintrc.js— shared base config
Four configs, all slightly different, all occasionally conflicting, all slow.
Why Biome
Biome is a single binary that does linting and formatting. No plugins. No config inheritance chains. No peerDependency hell. It's written in Rust, which means it's fast enough that you stop thinking about speed entirely.
The migration was PR #2, and it was surprisingly mechanical:
- Remove every ESLint and Prettier package from
package.json - Delete every
.eslintrc.*and.prettierrc.*file - Add one
biome.jsonat the root - Run
biome check --write .and fix the handful of legitimate issues it found - Update CI
The one biome.json now covers the entire monorepo:
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": { "recommended": true }
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
}
}That's it. That's the entire linting and formatting config for a monorepo with three apps and a shared backend.
The Speed Difference
Before (ESLint + Prettier):
- Mobile app lint: ~12 seconds
- Web app lint: ~8 seconds
- Full monorepo: ~25 seconds
After (Biome):
- Full monorepo: ~800ms
Not a typo. Sub-second for the entire codebase. CI pipelines that used to block on linting now finish before you can switch tabs.
What We Lost (And Why It's Fine)
Biome doesn't have the ESLint ecosystem. No eslint-plugin-react-hooks to yell at you about dependency arrays. No eslint-plugin-jsx-a11y for accessibility warnings. We accepted this trade-off because:
- TypeScript catches most of what ESLint plugins catch, just differently
- We'd rather have fast, consistent linting than comprehensive, slow linting
- The rules we actually enforced (import ordering, formatting, basic correctness) Biome handles natively
In the same PR, we also fixed the React Native build pipeline — adding build:android, build:ios, and proper Turborepo task configuration. The kind of foundational work that's invisible until it breaks.
The Takeaway
Developer tooling migrations are unsexy work. Nobody's writing a conference talk about switching linters. But the compound effect of 800ms lint times vs 25-second lint times, across every save, every commit, every CI run, for every developer — that's hundreds of hours over the life of a project.
Sometimes the best engineering decision is deleting config files.
Abdul Rafay Founder, Syntax Lab Technology