Biome Migration
What We Did
Section titled “What We Did”Replaced ESLint and Prettier with Biome, a fast all-in-one linter and formatter written in Rust. This consolidates two tools into one while providing significant performance improvements.
Why Biome
Section titled “Why Biome”Key reasons:
- Performance: Biome is 10-100x faster than ESLint + Prettier combined (written in Rust)
- Unified tooling: Single tool for both linting and formatting reduces configuration complexity
- Zero dependencies: No plugin ecosystem to manage or version conflicts to resolve
- Better defaults: Sensible rules out of the box with easy customization
Alternatives considered:
- Keep ESLint + Prettier: More ecosystem support but slower and more complex configuration
- ESLint with built-in formatting: Still slower than Biome, less mature formatting
- oxlint: Fast but less mature, no formatting support
Commands Used
Section titled “Commands Used”# Install Biome as a root dev dependencybun add -D @biomejs/biome
# Remove Prettier (no longer needed)bun remove prettier
# Run format with Biomebun format
# Run lint with Biomebun lintImplementation Details
Section titled “Implementation Details”Package Structure
Section titled “Package Structure”Created a new @repo/biome-config package to share configurations across the monorepo:
packages/biome-config/├── package.json├── base.json # Core rules for all packages├── next.json # Next.js + React rules (extends base)└── react-internal.json # React library rules (extends base)Configuration Files
Section titled “Configuration Files”packages/biome-config/package.json:
{ "name": "@repo/biome-config", "version": "0.0.0", "private": true, "exports": { "./base": "./base.json", "./next-js": "./next.json", "./react-internal": "./react-internal.json" }}packages/biome-config/base.json (key settings):
{ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "organizeImports": { "enabled": true }, "linter": { "enabled": true, "rules": { "recommended": true, "correctness": { "noUnusedImports": "warn", "noUnusedVariables": "warn" }, "style": { "noNonNullAssertion": "off" }, "suspicious": { "noExplicitAny": "warn" } } }, "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 80 }, "javascript": { "formatter": { "semicolons": "asNeeded", "quoteStyle": "double", "trailingCommas": "es5" } }}Consumer configs (e.g., apps/web/biome.json):
{ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "extends": ["@repo/biome-config/next-js"]}Files Created
Section titled “Files Created”| File | Purpose |
|---|---|
packages/biome-config/package.json | Config package manifest |
packages/biome-config/base.json | Core lint/format rules |
packages/biome-config/next.json | Next.js + React rules |
packages/biome-config/react-internal.json | React library rules |
biome.json (root) | Workspace-level config |
apps/web/biome.json | Web app config |
packages/ui/biome.json | UI package config |
Files Deleted
Section titled “Files Deleted”| File | Reason |
|---|---|
packages/eslint-config/ (entire directory) | Replaced by biome-config |
apps/web/eslint.config.js | Using biome.json instead |
packages/ui/eslint.config.mjs | Using biome.json instead |
Script Changes
Section titled “Script Changes”Root package.json:
"format": "prettier --write \"**/*.{ts,tsx,md}\"""format": "biome format --write ."apps/web/package.json and packages/ui/package.json:
"lint": "eslint --max-warnings 0""lint": "biome check --write"Key Dependencies
Section titled “Key Dependencies”@biomejs/biome: ^1.9.4 - All-in-one linter and formatter
Removed:
prettier: ^3.7.4 - Replaced by Biome formattereslint: ^9.39.1 - Replaced by Biome linter@repo/eslint-config: Replaced by@repo/biome-config
Integration with Existing Code
Section titled “Integration with Existing Code”The migration required minimal code changes:
-
Fixed lint errors surfaced by Biome:
packages/ui/src/components/ui/field.tsx: Changed==to===for strict equalitypackages/ui/src/components/ui/field.tsx: Usederror.messageas key instead of array index
-
Config inheritance:
- Each config extends the base with additional rules
next.jsonadds React hooks and a11y rulesreact-internal.jsondisablesnoLabelWithoutControlfor component libraries
Context for AI
Section titled “Context for AI”When working with linting and formatting:
- Use
bun lintto run Biome linting across all packages - Use
bun formatto format all files with Biome - Use
biome check --writeto lint AND format in one command - Biome configs use JSON (not JavaScript), so no dynamic configuration
- The
extendsfield in child configs points to the package export path
Key configuration decisions:
semicolons: "asNeeded"- No semicolons unless required for ASInoNonNullAssertion: "off"- Allow!assertions (common in env vars)noExplicitAny: "warn"- Discourage but don’t blockanytypesnoLabelWithoutControl: "off"(react-internal) - Labels gethtmlForvia props
Outcomes
Section titled “Outcomes”Before
Section titled “Before”- Two separate tools: ESLint for linting, Prettier for formatting
- Complex plugin ecosystem with version compatibility issues
- Slower execution (JavaScript-based tools)
- Multiple config files per package
- Single tool handles both linting and formatting
- No plugin dependencies to manage
- 10-100x faster execution
- Simpler JSON-based configuration
- Consistent code style enforced automatically
Testing/Verification
Section titled “Testing/Verification”# Verify lint passesbun lint
# Verify format worksbun format
# Verify build still worksbun run build
# Test specific packagesturbo lint --filter=webturbo lint --filter=@repo/uiExpected results:
- All lint commands pass without errors
- Format command completes and fixes files
- Build completes successfully
- No semicolons in formatted code
Related Documentation
Section titled “Related Documentation”- Biome Documentation
- Biome Configuration Reference
- Turborepo with-biome example
- Initial Turborepo Setup - Original ESLint setup