Introduction
The modern web development toolchain is a tower of complexity. A typical project juggles ESLint for linting, Prettier for formatting, TypeScript for type checking, PostCSS for CSS processing, and various bundlers to tie it all together. Each tool has its own configuration format, plugin ecosystem, and version compatibility matrix. Biome challenges this status quo by offering a single, Rust-powered tool that handles JavaScript/TypeScript linting, formatting, and import organization with dramatically better performance.
What sets Biome apart from other attempts to unify the toolchain is its pragmatic approach. Rather than trying to replace everything at once, Biome focuses on doing the core developer workflow tasks—lint, format, sort imports—extremely well. The result is a tool that's not just faster, but simpler to configure, easier to maintain, and more consistent in its output. This guide explores Biome's capabilities for web development and how to integrate it into your existing workflow.
Understanding Biome: Core Concepts
The All-in-One Philosophy
Biome's core value proposition is consolidation. Instead of maintaining separate configurations for ESLint, Prettier, and import sorting plugins, you maintain a single biome.json. This reduces configuration drift between team members, eliminates version conflicts between plugins, and makes onboarding new developers trivial—there's one tool to learn, one configuration to understand.
Consider a typical team's toolchain overhead: ESLint requires .eslintrc.js with dozens of plugin dependencies, Prettier needs .prettierrc with its own opinionated defaults that sometimes conflict with ESLint, and import sorting needs eslint-plugin-import or a standalone tool like @trivago/prettier-plugin-sort-imports. Each dependency brings its own transitive dependencies, version constraints, and potential breaking changes during upgrades. Biome collapses all of this into a single binary with a single configuration file.
Built for the Web Platform
Biome understands modern web development idioms out of the box. It handles JSX and TSX natively, understands React hooks rules, catches accessibility issues in HTML/JSX, and formats CSS (with HTML support on the roadmap). You don't need to install framework-specific plugins for React, Vue, or Svelte—Biome's rules apply universally to the JavaScript/TypeScript layer.
The Speed Advantage
Biome is written in Rust, which gives it a fundamental speed advantage over Node.js-based tools. On a typical web project with 500 files, Biome completes linting and formatting in under 1 second, compared to 15-30 seconds for ESLint + Prettier. This speed difference isn't just a benchmark curiosity—it changes how developers interact with their toolchain. When linting is instant, you run it after every save instead of only in CI.
The performance advantage scales with project size. On monorepos with thousands of files, the difference becomes even more pronounced. Teams working on large Next.js or Remix applications report CI pipeline reductions of 30-60 seconds per run, which compounds across hundreds of daily builds.
Architecture and Design Patterns
Lossless Concrete Syntax Tree
Biome parses source code into a lossless CST that preserves every whitespace character, comment, and formatting detail. This is fundamentally different from ESLint's AST, which discards this information. The lossless tree enables Biome to make precise formatting decisions without losing track of where comments are or how blank lines separate logical sections.
This architecture choice has practical implications. When Biome reformats your code, it preserves the intent behind your formatting decisions—comment alignment, intentional blank lines between logical sections, and trailing comments on specific lines all survive the formatting pass. Prettier, by contrast, sometimes repositions or reflows comments in ways that lose developer intent.
Incremental Parsing
When you change a single line in a file, Biome only re-parses the modified portion. This incremental parsing powers the language server's real-time feedback—lint errors and format suggestions appear as you type, with no perceptible delay. The language server protocol (LSP) integration means Biome works in any editor that supports LSP, not just VS Code.
Single-Pass Pipeline
Biome's pipeline is: parse → lint → format → emit. Each stage operates on the same in-memory CST. There's no serialization between stages, no temporary files, and no redundant parsing. This single-pass architecture is why Biome is 10-20x faster than running ESLint and Prettier sequentially.
Import Organization
Biome's import organizer groups and sorts imports according to configurable rules. It separates third-party imports from relative imports, sorts within groups, and handles type-only imports correctly. This is built into the same pipeline as linting and formatting, so there's no additional tool or plugin to install.
The import organization is particularly smart about understanding your project's module structure. It detects which imports are side-effect-only (like CSS imports), which are type imports, and which are value imports, and groups them accordingly without breaking your code's runtime behavior.
Step-by-Step Implementation
Quick Start for a New Project
# Create a new project
mkdir my-web-app && cd my-web-app
npm init -y
npm install --save-dev @biomejs/biome typescript
# Initialize Biome
npx @biomejs/biome initMigrating from ESLint + Prettier
The migration from an existing ESLint + Prettier setup requires careful planning. Start by running Biome's built-in migration command:
# Migrate from ESLint and Prettier configurations
npx @biomejs/biome migrate --writeThis command reads your .eslintrc.* and .prettierrc.* files and generates an equivalent biome.json. It maps ESLint rules to Biome equivalents where possible and flags rules that don't have a direct counterpart. After migration, review the generated configuration and adjust rules that were approximated.
For a team migration, follow this sequence:
- Run the migration tool and review the generated
biome.json - Run
biome check --write .to auto-fix all issues across the codebase - Commit the formatting changes in a dedicated commit with a clear message
- Add the commit hash to
.git-blame-ignore-revsto keepgit blameclean - Remove ESLint, Prettier, and their plugins from
package.json - Update CI pipelines to use
biome ci .instead of separate lint and format checks - Update the team's VS Code settings to use the Biome extension
# .git-blame-ignore-revs
# Biome migration - bulk formatting
a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2Configuration for a React + TypeScript Project
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"organizeImports": {
"enabled": true,
"rules": {
"recommended": true
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useAltText": "error",
"useButtonType": "error",
"useValidAriaRole": "error",
"noBlankTarget": "error"
},
"correctness": {
"useExhaustiveDependencies": "error",
"useHookAtTopLevel": "error",
"noUnusedImports": "error",
"noUnusedVariables": "error"
},
"style": {
"useConst": "error",
"noNonNullAssertion": "warn",
"useImportType": "error",
"useNamingConvention": "error"
},
"suspicious": {
"noExplicitAny": "warn",
"noConsoleLog": "warn"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"jsxQuoteStyle": "double",
"arrowParentheses": "always"
}
},
"css": {
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
}
},
"files": {
"ignore": [
"node_modules",
"dist",
"build",
".next",
"coverage",
"public",
"*.min.js",
"*.min.css"
]
}
}VS Code Integration
Install the Biome VS Code extension and configure it as the default formatter:
// .vscode/settings.json
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"quickfix.biome": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
}
}GitHub Actions CI
# .github/workflows/biome.yml
name: Code Quality
on: [push, pull_request]
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx biome ci .Real-World Use Cases
Next.js Application
A Next.js 14 application with 300 components and 50 API routes migrated from ESLint + Prettier to Biome in one day. The team reported that the VS Code extension's real-time feedback was noticeably faster—lint errors appeared as they typed instead of after a 2-second delay. CI time dropped from 45 seconds to 3 seconds.
The team also discovered that Biome's useExhaustiveDependencies rule caught several subtle bugs in their useEffect hooks that ESLint's exhaustive-deps rule had been missing due to a plugin version mismatch. These were real bugs affecting production behavior—in one case, a component was re-fetching data on every render because a dependency was incorrectly omitted.
Vite + React SPA
A single-page application built with Vite and React adopted Biome from the start. The team used biome check --write as their pre-commit hook, ensuring every commit was properly formatted and linted. The zero-configuration defaults for React JSX were a perfect match for their coding style.
The speed advantage was particularly impactful during code reviews. Reviewers could run biome ci --changed (on the feature branch) in under a second to verify code quality, compared to the 20-second ESLint run that had previously discouraged frequent checks.
Remix Full-Stack App
A Remix application with server-side loaders and client-side components used Biome to enforce consistent import ordering between server and client modules. The import organization rules prevented circular dependencies between server and client code paths.
The team leveraged Biome's overrides configuration to apply stricter rules to server-side code (no console.log, stricter type checking) while allowing more relaxed rules in client components during rapid prototyping.
Design System Library
A component library with 80 React components used Biome to enforce naming conventions (PascalCase for components, camelCase for utilities) and accessibility rules. The a11y rule category caught 23 accessibility issues that ESLint had missed due to missing plugin configuration.
The library maintainers also used Biome's CSS formatter to ensure consistent styling across their CSS Modules, eliminating debates about indentation and spacing in style files.
Monorepo with Turborepo
A Turborepo monorepo with 12 packages (shared UI, utilities, API, and multiple Next.js apps) centralized Biome configuration at the root level with per-package overrides. This eliminated the configuration drift that had plagued their previous ESLint setup, where each package had subtly different rule configurations.
{
"overrides": [
{
"include": ["packages/shared-ui/**"],
"linter": {
"rules": {
"a11y": {
"useAltText": "error",
"useButtonType": "error",
"useSemanticElements": "error"
}
}
}
},
{
"include": ["apps/admin/**"],
"linter": {
"rules": {
"suspicious": {
"noConsoleLog": "off"
}
}
}
}
]
}Best Practices for Production
-
Use
biome checkas your single CI command — It runs linting, formatting, and import organization in one pass. No need for separateeslintandprettier --checkcommands. -
Configure format-on-save in your editor — Biome's speed makes format-on-save practical. There's no perceptible delay, and it ensures every file is always properly formatted.
-
Enable
useImportTyperule — This enforces TypeScript'simport typesyntax, which helps bundlers tree-shake unused types and reduces bundle size. -
Use
overridesfor different file types — Apply different rules to test files, configuration files, and generated code without creating separate configuration files. -
Commit a migration rev to
.git-blame-ignore-revs— After the initial migration, add the formatting commit hash to this file sogit blameignores the bulk formatting changes. -
Use
--writefor auto-fixing —biome check --writeauto-fixes all fixable issues. This is safe to run because Biome's auto-fixes preserve code semantics. -
Review nursery rules carefully — Nursery rules are experimental and may have false positives. Enable them selectively and report issues to the Biome team.
-
Keep the binary out of version control — Add
node_modules/.bin/biometo.gitignore. Letnpm installhandle downloading the platform-specific binary. -
Use
--changedfor incremental checks — In CI, usebiome ci --changedto only check files that changed in the current branch. This keeps CI fast on large codebases. -
Document rule decisions — When disabling a rule, add a comment explaining why. Biome supports
// biome-ignore lint/style/noNonNullAssertion: reasonsyntax for inline suppressions.
Common Pitfalls and Solutions
| Pitfall | Impact | Solution |
|---|---|---|
| ESLint plugins with no Biome equivalent | Missing lint rules | Keep a minimal ESLint config for gaps, check Biome's rule roadmap |
| Prettier formatting differences | Large diff on migration | Run biome format --write . once, commit, and use git blame --ignore-rev |
| Conflicting VS Code extensions | ESLint and Biome both active | Disable ESLint extension for files handled by Biome |
| Import sorting changes | Reordered imports in every file | Accept the new order, commit once, and move on |
| CSS formatting not yet complete | Limited CSS rule coverage | Use PostCSS or stylelint for CSS-specific needs until Biome's CSS support matures |
| Team resistance to new tooling | Slow adoption | Run both tools in parallel for a sprint, then switch once the team is comfortable |
| Custom ESLint rules | No direct Biome equivalent | Use Biome's biome-ignore comments for now, or write a custom Biome plugin when the API stabilizes |
| Editor integration issues | Lint errors not showing | Ensure the Biome extension is enabled and ESLint extension is disabled for JS/TS files |
Performance Optimization
Real-World Benchmarks
Tested on a production React application with 800 files:
| Operation | ESLint + Prettier | Biome | Speedup |
|---|---|---|---|
| Full lint | 28s | 1.8s | 15.5x |
| Full format | 22s | 1.1s | 20x |
| Lint + format + import sort | 55s | 2.5s | 22x |
| CI (changed files) | 6s | 0.3s | 20x |
| VS Code diagnostics | 2-3s delay | Instant | ~10x |
Memory and Disk Usage
| Metric | ESLint + Prettier | Biome |
|---|---|---|
| node_modules size | 380 MB | 8 MB |
| Peak memory (lint) | 1.1 GB | 150 MB |
| Startup time | 800ms | 15ms |
| Config files | 6 files | 1 file |
Why Rust Matters
The performance gap isn't just about language speed—it's about architecture. ESLint and Prettier are built on Node.js, which means they pay the cost of JavaScript's runtime overhead: garbage collection pauses, JIT warmup, and module resolution. Biome's Rust implementation avoids all of this, and its single-pass architecture means there's no redundant work.
The memory savings are equally important. On CI runners with limited memory (GitHub Actions gives 7GB), running ESLint on a large project can consume 1-2GB, leaving less room for tests and builds. Biome's 150MB footprint is negligible by comparison.
Comparison with Alternatives
| Feature | Biome | ESLint + Prettier | Deno (built-in) | oxlint + dprint |
|---|---|---|---|---|
| Single tool | Yes | No | Yes (runtime) | No |
| Speed | Very fast | Slow | Fast | Very fast |
| JSX/React support | Native | Plugin | Native | Native |
| CSS support | Basic | Plugin | No | dprint only |
| Import sorting | Built-in | Plugin | No | No |
| Multi-file analysis | Yes | Limited | No | No |
| Configuration | biome.json | Multiple files | deno.json | Multiple files |
| Maturity | Growing | Very mature | Growing | Early |
| VS Code extension | Yes | Yes (separate) | Yes | Yes (separate) |
| Auto-fix support | Yes | Yes | Yes | Yes |
| Custom rules | Planned (plugins) | Yes (ESLint plugins) | Yes (lint rules) | No |
When to Choose Biome
- New projects: Start with Biome from day one. There's no migration cost, and the defaults are excellent for most web projects.
- ESLint + Prettier fatigue: If your team spends more time configuring tools than writing code, Biome eliminates the configuration burden.
- Large codebases: The performance advantages compound with project size. Monorepo teams see the biggest improvements.
- CI-focused teams: If your CI pipeline is bottlenecked by linting, Biome's speed can save minutes per build.
When to Stick with ESLint
- Custom rules: If you rely heavily on custom ESLint plugins for domain-specific rules, Biome's plugin system isn't ready yet.
- Framework-specific rules: Some frameworks (Angular, Svelte) have dedicated ESLint plugins with rules Biome doesn't cover.
- Gradual migration: If your team isn't ready for a full migration, you can run Biome alongside ESLint for incremental adoption.
Advanced Patterns
Monorepo with Package-Level Overrides
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"linter": {
"rules": {
"recommended": true
}
},
"overrides": [
{
"include": ["packages/ui/**"],
"linter": {
"rules": {
"a11y": {
"useAltText": "error",
"useButtonType": "error"
}
}
}
},
{
"include": ["packages/api/**"],
"linter": {
"rules": {
"suspicious": {
"noConsoleLog": "off"
}
}
}
},
{
"include": ["**/*.test.ts", "**/*.test.tsx"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off"
},
"style": {
"noNonNullAssertion": "off"
}
}
}
}
]
}Pre-commit Hook with Husky
# Install husky and lint-staged
npm install --save-dev husky lint-staged
npx husky init
# .husky/pre-commit
npx lint-staged// package.json
{
"lint-staged": {
"*.{js,ts,jsx,tsx,json,css}": "biome check --write --no-errors-on-unmatched"
}
}Editor-Agnostic Configuration with EditorConfig
For teams where not everyone uses VS Code, supplement Biome with an .editorconfig:
# .editorconfig
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = trueBiome respects .editorconfig settings as fallbacks when biome.json doesn't specify a value, ensuring consistent behavior across editors.
Testing Strategies
Verifying Your Configuration
# Check configuration validity
npx biome rage
# Run all checks with verbose diagnostics
npx biome check --diagnostic-level=verbose .
# Test a specific rule
npx biome lint --only=a11y/useAltText .
# Verify formatting without modifying files
npx biome format --check .
# Check import organization
npx biome check --linter-enabled=false --formatter-enabled=false .Continuous Integration Best Practices
Set up your CI pipeline to fail fast on quality issues:
# .github/workflows/quality.yml
name: Quality Checks
on: [pull_request]
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for --changed
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Check changed files
run: npx biome ci --changed
- name: Full project check
run: npx biome ci .The --changed flag checks only files that differ from the base branch, providing fast feedback during development. The full project check runs as a safety net for edge cases.
Future Outlook
Biome's roadmap includes full CSS linting and formatting, HTML support, and potentially a bundler. The CSS support is particularly exciting for web developers—imagine linting and formatting your CSS, JavaScript, and TypeScript with the same tool and configuration.
The plugin system, planned for 2025, will allow teams to write custom lint rules in Rust or WASM. This unlocks the long tail of enterprise-specific rules that currently require custom ESLint plugins.
The broader trend is clear: the JavaScript toolchain is consolidating around Rust-based tools. Biome, Vite's Rolldown, OXC, and Rspack are all converging toward a future where a small number of fast, native tools replace the sprawling Node.js-based toolchain. Biome is the furthest along in this consolidation for the lint/format domain.
Biome 2.0, released in early 2025, added significant improvements to CSS formatting, multi-file analysis for detecting unused exports across project boundaries, and a new diagnostic format that provides more actionable error messages. The team's release cadence—roughly monthly minor releases with weekly patch releases—demonstrates the project's momentum and reliability.
Conclusion
Biome is the tool the JavaScript ecosystem has been waiting for. The key takeaways:
- One tool, one config — Replace ESLint, Prettier, and import sorting plugins with a single
biome.json - Speed enables better workflows — When linting and formatting take milliseconds, you run them constantly instead of only in CI
- Web-first features — Native JSX/React support, accessibility rules, and CSS formatting make Biome ideal for web development
- Migration is a one-time cost — The migration tool handles most of the conversion, and the remaining gaps close with each release
- The future is Rust-based — Biome is part of a broader trend toward native tooling that will define the next decade of JavaScript development
Start by running npx @biomejs/biome init on your next project. Configure format-on-save in VS Code and experience the speed difference. Within a week, you'll wonder how you ever tolerated the old toolchain.