- Replace .eslintrc.cjs with eslint.config.mjs (ESLint 9 flat config)
using direct eslint-plugin-solid + @typescript-eslint/parser approach
- Add @typescript-eslint/parser to root devDependencies
- Add main/module/types top-level fields to packages/core/package.json
- Add resolve.conditions to packages/core/vite.config.ts
- Create packages/core/tsconfig.test.json for test type-checking
- Remove empty paths:{} from packages/core/tsconfig.json
305 lines
10 KiB
Markdown
305 lines
10 KiB
Markdown
[Skip to main content](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026#main-content)
|
|
|
|
## [TL;DR](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#tldr)
|
|
|
|
**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](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#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](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#speed-the-main-reason-to-switch)
|
|
|
|
```bash
|
|
# 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](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#setup-one-config-vs-two-configs)
|
|
|
|
```json
|
|
// 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](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#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?](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#prettier-compatibility-how-close-is-it)
|
|
|
|
```javascript
|
|
// 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](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#integration-with-editors-and-ci)
|
|
|
|
```bash
|
|
# 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](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#migration-from-eslint--prettier)
|
|
|
|
```bash
|
|
# 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?](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#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](https://www.pkgpulse.com/)._
|
|
|
|
See the live comparison
|
|
|
|
[View biome vs. eslint on PkgPulse →](https://www.pkgpulse.com/compare/biome-vs-eslint)
|
|
|
|
## Comments
|
|
|
|
### 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.
|
|
|
|
Get the Free Cheatsheet |