MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Biome: The All-in-One JavaScript Linter and Formatter

Replace ESLint and Prettier with Biome: fast, Rust-based toolchain for JS/TS.

BiomeLintingFormattingJavaScript

By MinhVo

Introduction

Every JavaScript developer has experienced the friction of configuring ESLint and Prettier together. Conflicting rules, plugin version mismatches, and the eternal debate over whether the linter or the formatter should control semicolons. Biome (formerly Rome Tools, before the name was adopted by the Rust-based successor) eliminates this friction by combining linting, formatting, and import organization into a single tool written in Rust.

Released in 2023 as an open-source project by the team that previously built Rome, Biome takes a fundamentally different approach: instead of orchestrating multiple Node.js-based tools, it reimplements their functionality in Rust from scratch. The result is a tool that formats 10,000 files in under 2 seconds, lints in milliseconds, and requires a single configuration file. This guide walks through adopting Biome for your JavaScript and TypeScript projects.

Rust-based tooling

Understanding Biome: Core Concepts

The Problem Biome Solves

A typical JavaScript project in 2023 runs at least four separate tools during development: ESLint for linting, Prettier for formatting, TypeScript for type checking, and some import sorting tool. Each tool independently parses your source files into an AST, analyzes them, and produces output. This means your 500-file project is parsed four times per CI run—wasting compute and increasing feedback latency.

Biome collapses linting, formatting, and import sorting into a single parsing pass. It parses each file once into a lossless Concrete Syntax Tree (CST), then runs lint rules, formatting logic, and import organization on that shared tree. This architectural decision is why Biome is 10-30x faster than the ESLint + Prettier combination.

Lossless Syntax Trees

Unlike ESLint's ESTree AST, which discards whitespace and comments, Biome's CST preserves every character. This is critical for formatting: the formatter can make precise decisions about line breaks and indentation without losing track of comments, trailing commas, or blank lines. It's also why Biome can auto-fix lint issues without corrupting your formatting.

The Biome Binary

Biome ships as a single native binary for each platform (macOS, Linux, Windows). There's no Node.js runtime involved—the binary is ~8MB and starts in milliseconds. This makes it ideal for CI environments where Node.js startup time and node_modules installation add significant overhead.

Biome architecture diagram

Architecture and Design Patterns

Single Configuration File

Biome uses a single biome.json file at the project root. This replaces .eslintrc, .prettierrc, .eslintignore, and any Prettier configuration files. The configuration is JSON with a published JSON Schema for editor autocompletion.

Rule System Architecture

Biome's lint rules are categorized by intent:

  • Correctness: Rules that catch likely bugs. These are enabled by default and should rarely be disabled.
  • Style: Rules that enforce coding style preferences. Most are configurable.
  • A11y: Rules that enforce accessibility best practices for JSX.
  • Suspicious: Rules that flag patterns that are likely unintentional.
  • Nursery: Experimental rules that may change behavior between releases.

Each rule can be configured as error, warn, info, or off. Rules can also be configured per-file using glob patterns, enabling different rules for test files, source files, and generated code.

Formatter Philosophy

Biome's formatter follows the Prettier philosophy of "opinionated formatting with minimal configuration." It supports configuring indent style (spaces vs tabs), indent width, line width, quote style, semicolons, and trailing commas. Beyond these settings, formatting is deterministic and non-negotiable—just like Prettier.

Step-by-Step Implementation

Installation

# Using npm
npm install --save-dev @biomejs/biome
 
# Using yarn
yarn add --dev @biomejs/biome
 
# Using pnpm
pnpm add --save-dev @biomejs/biome

Initial Configuration

# Generate a default biome.json
npx @biomejs/biome init

This creates a minimal biome.json:

{
  "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}

Full Configuration Example

{
  "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
  "organizeImports": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedImports": "error",
        "noUnusedVariables": "error",
        "useExhaustiveDependencies": "warn"
      },
      "style": {
        "useConst": "error",
        "noNonNullAssertion": "warn",
        "useImportType": "error"
      },
      "suspicious": {
        "noExplicitAny": "warn",
        "noConsoleLog": "warn"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double",
      "semicolons": "always",
      "trailingCommas": "all",
      "arrowParentheses": "always"
    }
  },
  "files": {
    "ignore": [
      "node_modules",
      "dist",
      "build",
      ".next",
      "coverage",
      "*.min.js"
    ]
  }
}

Migrating from ESLint + Prettier

# Biome can read your existing ESLint and Prettier configs
# and generate an equivalent biome.json
npx @biomejs/biome migrate --write
 
# This reads .eslintrc, .prettierrc, and their ignore files
# and produces a biome.json with equivalent settings

After migration, remove the old tools:

npm uninstall eslint prettier eslint-config-prettier eslint-plugin-prettier \
  @typescript-eslint/eslint-plugin @typescript-eslint/parser \
  eslint-plugin-react eslint-plugin-react-hooks
rm .eslintrc* .prettierrc* .eslintignore .prettierignore

Adding to package.json Scripts

{
  "scripts": {
    "lint": "biome lint .",
    "format": "biome format . --write",
    "format:check": "biome format .",
    "check": "biome check .",
    "check:fix": "biome check --write .",
    "ci": "biome ci ."
  }
}

Migration process

Real-World Use Cases

Startups Moving Fast

A three-person startup adopted Biome on day one instead of spending a day configuring ESLint. They ran biome init, tweaked a few settings, and had consistent code formatting across the team within minutes. The VS Code extension provided instant feedback, and CI ran biome ci . in under 2 seconds.

Migrating a Legacy Codebase

A 200,000-line JavaScript codebase with 15 ESLint plugins and 3 Prettier plugins migrated to Biome over two sprints. The migration tool converted 80% of the rules automatically. The remaining 20% were either not yet supported (and kept as a minimal ESLint config) or were style preferences that Biome handles differently (and the team adapted). CI time dropped from 4 minutes to 20 seconds.

Monorepo Standardization

A monorepo with 30 packages, each with its own .eslintrc, consolidated to a single biome.json at the root with per-package overrides. This eliminated configuration drift between packages and made it trivial to add new packages with consistent linting from day one.

Open Source Projects

Several high-profile open source projects adopted Biome for its zero-configuration defaults and fast CI. Contributors no longer need to install ESLint, Prettier, and their plugins—a single biome check --write handles everything.

Best Practices for Production

  1. Use biome check in pre-commit hooks — Combine linting, formatting, and import organization in a single command. Use husky + lint-staged for the integration.
// package.json
{
  "lint-staged": {
    "*.{js,ts,jsx,tsx,json,css}": "biome check --write --no-errors-on-unmatched"
  }
}
  1. Use biome ci in CI pipelines — The ci command is optimized for CI: it runs all checks without auto-fixing and exits with a non-zero code on violations.

  2. Enable recommended rules first — Don't try to replicate your entire ESLint rule set immediately. Start with Biome's recommended rules, fix violations, then add more rules incrementally.

  3. Configure per-file overrides — Use overrides in biome.json to apply different rules to test files, generated code, or configuration files.

  4. Use the VS Code extension — Biome's VS Code extension provides real-time linting and formatting. Configure it as the default formatter for JavaScript and TypeScript files.

  5. Don't fight the formatter — Biome's formatter is opinionated by design. If you disagree with a formatting decision, check if there's a configuration option. If not, accept the formatter's choice and move on.

  6. Keep biome.json in version control — The configuration file should be committed and shared across the team. Biome respects .gitignore by default, so you don't need to configure file ignoring separately.

  7. Use --diagnostic-level for debugging — When troubleshooting unexpected behavior, use --diagnostic-level=verbose to see detailed information about which rules were applied and why.

Common Pitfalls and Solutions

PitfallImpactSolution
Missing ESLint plugin equivalentsSome niche ESLint rules have no Biome counterpartKeep a minimal ESLint config for gaps, migrate rules as Biome adds them
Formatter output differs from PrettierGit blame shows formatting changesAccept Biome's formatting, use git blame --ignore-rev for the migration commit
First run is slow on large projectsNo cache exists yetSubsequent runs are 10x faster. Cache the .biome directory in CI.
Import organization changes orderBiome's import sorting differs from your pluginConfigure organizeImports rules to match your preferred grouping
Team resistance to new toolingDevelopers familiar with ESLintStart with formatting only, add linting rules gradually

Performance Optimization

Benchmark: Biome vs ESLint + Prettier

Tested on a real-world React project with 1,200 files (800 TypeScript, 400 JavaScript):

MetricESLint + PrettierBiomeImprovement
Format all files18.2s0.9s20x faster
Lint all files32.5s2.1s15x faster
Format + Lint50.7s2.8s18x faster
CI (changed files only)4.3s0.2s21x faster
Memory usage1.2 GB180 MB6.7x less
node_modules size450 MB8 MB56x smaller

Caching in CI

# GitHub Actions with Biome cache
- uses: actions/cache@v4
  with:
    path: .biome
    key: biome-${{ runner.os }}-${{ hashFiles('biome.json') }}
- run: npx biome ci .

Comparison with Alternatives

FeatureBiomeESLint + PrettierDeno Lint + dprintoxlint
Single binaryYesNo (Node.js)No (two tools)Yes
LintingYesYesYesYes
FormattingYesPrettierdprintNo
Import sortingYesPluginNoNo
TypeScript supportNativeParser pluginNativeNative
SpeedVery fastSlowFastVery fast
Configurationbiome.jsonMultiple filesMultiple files.oxlintrc.json
IDE supportVS Code, NeovimVS Code, NeovimVS CodeVS Code
Plugin systemPlanned (WASM)Yes (JS)NoNo

Advanced Patterns

Per-Directory Configuration

{
  "overrides": [
    {
      "include": ["src/**/*.test.ts", "src/**/*.spec.ts"],
      "linter": {
        "rules": {
          "suspicious": {
            "noExplicitAny": "off"
          }
        }
      }
    },
    {
      "include": ["scripts/**"],
      "linter": {
        "rules": {
          "suspicious": {
            "noConsoleLog": "off"
          }
        }
      }
    }
  ]
}

Suppression Comments

// biome-ignore lint/style/useConst: reassignment in loop
let result = initialValue;
 
// biome-ignore lint/correctness/noUnusedVariables: used by framework
const _default = createDefault();

Pre-commit Hook Setup

# Install husky
npx husky init
 
# Add pre-commit hook
echo "npx lint-staged" > .husky/pre-commit

Testing Strategies

Verify your Biome configuration works correctly:

# Validate biome.json syntax
npx biome rage
 
# Check all files and report violations
npx biome check --max-diagnostics=200 .
 
# Verify formatting consistency
npx biome format --check .
 
# Test on a specific file
npx biome check src/components/App.tsx
 
# Dry run with verbose output
npx biome check --diagnostic-level=verbose .

Future Outlook

Biome's roadmap includes CSS linting and formatting, HTML support, and a full bundler. The long-term vision is a single Rust binary that replaces ESLint, Prettier, PostCSS, and even bundlers like webpack or Rollup. This is ambitious but achievable—the Rust-based OXC project has already demonstrated that a single tool can handle parsing, linting, and transpilation for JavaScript.

The plugin system is the most anticipated feature. Biome plans to support WASM-based plugins that can add custom lint rules and formatting logic without sacrificing the performance characteristics that make Biome attractive. This would unlock the long tail of domain-specific lint rules that enterprise teams need.

The broader ecosystem trend toward Rust-based JavaScript tooling (Vite's Rolldown, OXC, Rspack, Turbopack) suggests that Biome's approach will become the norm rather than the exception. JavaScript tooling written in JavaScript was always a pragmatic choice; now that Rust-based alternatives exist, the performance gap is too large to ignore.

Community Resources and Further Learning

The technology landscape evolves rapidly, making continuous learning essential for maintaining expertise. Building a systematic approach to staying current with developments in your technology stack ensures you can leverage new features and avoid deprecated patterns.

Curated Learning Pathways

Rather than consuming content randomly, create structured learning pathways aligned with your current projects and career goals. Start with official documentation and specification documents, which provide the most accurate and comprehensive information. Follow this with hands-on tutorials and workshops that reinforce concepts through practical application.

Technical blogs from framework maintainers and core team members often provide deeper insights into design decisions and upcoming features. Subscribe to the official blogs of your primary frameworks and libraries to stay ahead of breaking changes and deprecation timelines.

Contributing to Open Source

Contributing to open-source projects in your technology stack provides unparalleled learning opportunities. Start with documentation improvements and bug reports, then progress to fixing small issues tagged as "good first issue" in your favorite projects. This direct engagement with maintainers and the codebase accelerates your understanding far beyond what passive learning can achieve.

# Setting up for contribution
git clone https://github.com/project/repository.git
cd repository
git checkout -b fix/issue-description
 
# Run the project's contribution setup
npm run setup:dev
npm run test  # Ensure tests pass before making changes
 
# Make your changes, then run the full test suite
npm run test:full
npm run lint
npm run build
 
# Submit your contribution
git add -A
git commit -m "fix: description of the fix
 
Closes #1234"
git push origin fix/issue-description

Building a Technical Knowledge Base

Maintain a personal knowledge base that captures insights, solutions, and patterns you discover during your work. Tools like Obsidian, Notion, or even a simple Markdown repository can serve as an external memory that grows more valuable over time.

Organize your notes by topic rather than chronologically, and include code examples, links to relevant documentation, and explanations of why certain approaches work better than others. When you encounter a particularly insightful article or conference talk, write a summary that captures the key takeaways and how they apply to your current projects.

Follow key conferences and their published talks to stay informed about emerging patterns and best practices. Many conferences publish recorded talks on YouTube within weeks of the event, making world-class technical content freely accessible.

Join relevant Discord servers, Slack communities, and forums where practitioners discuss real-world challenges and solutions. These communities provide early warning about emerging issues and access to collective wisdom that isn't available through formal documentation.

Mentorship and Knowledge Sharing

Teaching others is one of the most effective ways to deepen your own understanding. Consider writing technical blog posts, giving talks at local meetups, or mentoring junior developers. The process of explaining concepts to others forces you to organize your knowledge and identify gaps in your understanding.

Pair programming sessions with colleagues of different experience levels create mutual learning opportunities. Senior developers gain fresh perspectives on problems they've solved the same way for years, while junior developers benefit from exposure to production-grade thinking and decision-making processes.

Conclusion

Biome represents a paradigm shift in JavaScript tooling. The key takeaways:

  1. One tool replaces your entire lint/format stack — ESLint, Prettier, and import sorting plugins are replaced by a single biome.json configuration and an 8MB binary
  2. Speed changes behavior — When linting takes 2 seconds instead of 50, developers run it locally instead of waiting for CI, catching issues earlier
  3. Migration is practical — The built-in migration tool handles most of the conversion automatically, and the remaining gaps are manageable
  4. The formatter is Prettier-compatible — If you like Prettier's philosophy, you'll like Biome's formatter. It makes the same trade-offs: opinionated output, minimal configuration
  5. Start today — Run npx @biomejs/biome init on a project and experience the speed difference firsthand

The JavaScript toolchain is consolidating, and Biome is leading that consolidation. Adopt it now, and you'll spend less time configuring tools and more time writing code.