Biome: Replace ESLint and Prettier with One Ultra-Fast Tool

Your ESLint + Prettier pipeline takes 45 seconds? Biome does the same thing in under 2 seconds. Biome is an all-in-one formatter and linter written in Rust that replaces ESLint, Prettier, and their dozens of plugins. In this tutorial, you will migrate a complete Next.js project to Biome and discover why thousands of projects made the switch in 2026.
What You Will Learn
By the end of this tutorial, you will be able to:
- Understand what Biome does and why it replaces ESLint + Prettier
- Install and configure Biome in an existing JavaScript/TypeScript project
- Automatically migrate your ESLint and Prettier rules to Biome
- Set up custom linting rules per file and per directory
- Integrate Biome into VS Code with automatic formatting
- Add Biome to your CI/CD pipeline (GitHub Actions and GitLab CI)
- Use organized imports and automatic sorting
- Configure Biome for a monorepo with shared rules
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed (
node --version) - An existing JavaScript or TypeScript project (ideally with ESLint/Prettier)
- Familiarity with the command line
- VS Code or an editor with LSP support
- Basic knowledge of configuration files (JSON)
Why Biome?
In 2026, the majority of JavaScript projects use ESLint for linting and Prettier for formatting. This combination works, but it has concrete problems:
- Slow: ESLint parses each file in JavaScript, which is inherently slow on large projects
- Complex configuration: between
.eslintrc,.prettierrc,eslint-config-*, plugins, parsers, and ESLint/Prettier conflicts, configuration becomes a headache - Conflicts: ESLint and Prettier can contradict each other — you need
eslint-config-prettierto disable conflicting rules - Maintenance: each tool has its own update cycle, breaking changes, and plugins to maintain
Biome solves all of this with a different approach:
| Aspect | ESLint + Prettier | Biome |
|---|---|---|
| Language | JavaScript (slow) | Rust (fast) |
| Tools | 2 tools + plugins | 1 single tool |
| Config | 2-4 files | 1 file biome.json |
| Speed | ~45s on 1000 files | ~1.5s on 1000 files |
| Conflicts | Frequent | Impossible |
Step 1: Install Biome
Let us create a new project for demonstration, or use your existing project:
# Create a test Next.js project
npx create-next-app@latest biome-demo --typescript --tailwind --app --src-dir
cd biome-demoInstall Biome as a dev dependency:
npm install --save-dev --save-exact @biomejs/biomeThe exact version is recommended (--save-exact) because Biome follows semantic versioning and minor updates can add new rules that generate errors in your CI.
Initialize the configuration:
npx @biomejs/biome initThis creates a biome.json file at the project root:
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}Step 2: Configure the Formatter
Biome uses tabs by default, but most JavaScript projects use 2 spaces. Let us configure the formatter to match common conventions:
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always",
"bracketSpacing": true,
"jsxQuoteStyle": "double"
}
}
}Comparison with Prettier
If you are coming from Prettier, here is the option mapping:
| Prettier | Biome | Default |
|---|---|---|
tabWidth | indentWidth | 2 |
useTabs | indentStyle: "tab" | "space" |
printWidth | lineWidth | 80 |
singleQuote | quoteStyle: "single" | "double" |
semi | semicolons | "always" |
trailingComma | trailingCommas | "all" |
endOfLine | lineEnding | "lf" |
Test the formatter on your project:
# Format and write changes
npx @biomejs/biome format --write .
# Check without modifying (dry run)
npx @biomejs/biome format .Step 3: Configure the Linter
Biome's linter includes over 300 rules, covering most popular rules from eslint, typescript-eslint, eslint-plugin-react, eslint-plugin-react-hooks, and eslint-plugin-jsx-a11y.
Recommended Rules
By default, "recommended": true enables a set of safe, consensus-based rules:
{
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}Customize Rules
You can adjust each rule individually:
{
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveCognitiveComplexity": {
"level": "warn",
"options": {
"maxAllowedComplexity": 20
}
}
},
"style": {
"noNonNullAssertion": "warn",
"useConst": "error",
"useImportType": "error",
"noParameterAssign": "error"
},
"suspicious": {
"noExplicitAny": "warn",
"noConsoleLog": "warn"
},
"correctness": {
"noUnusedVariables": "error",
"noUnusedImports": "error",
"useExhaustiveDependencies": "warn"
},
"nursery": {
"useSortedClasses": {
"level": "warn",
"options": {
"attributes": ["className"],
"functions": ["clsx", "cn", "cva"]
}
}
}
}
}
}Rules in the nursery category are experimental and may change between minor versions. Use them in warn mode rather than error in your CI.
Rule Categories
Biome organizes its rules into clear categories:
| Category | Description | Examples |
|---|---|---|
correctness | Likely errors | noUnusedVariables, useExhaustiveDependencies |
suspicious | Suspicious code | noExplicitAny, noConsoleLog |
style | Code conventions | useConst, useImportType |
complexity | Overly complex code | noExcessiveCognitiveComplexity |
performance | Optimizations | noAccumulatingSpread, noDelete |
security | Vulnerabilities | noDangerouslySetInnerHtml |
a11y | Accessibility | useAltText, useAriaProps |
nursery | Experimental | useSortedClasses |
Run the linter:
# Lint only
npx @biomejs/biome lint .
# Lint with auto-fixes
npx @biomejs/biome lint --write .
# Lint + format + imports in one command
npx @biomejs/biome check --write .Step 4: Migrate from ESLint and Prettier
If you have an existing project with ESLint and Prettier, Biome provides an automatic migration tool.
Automatic Migration
# Migrate ESLint configuration to Biome
npx @biomejs/biome migrate eslint --write
# Migrate Prettier configuration to Biome
npx @biomejs/biome migrate prettier --writeThis reads your .eslintrc.* and .prettierrc.* files, then updates biome.json with the equivalent rules.
Concrete Example
Suppose this ESLint configuration:
// .eslintrc.json (before)
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn",
"react-hooks/exhaustive-deps": "warn",
"no-console": "warn"
}
}After migration, Biome generates:
// biome.json (after migration)
{
"linter": {
"rules": {
"recommended": true,
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "warn"
},
"suspicious": {
"noExplicitAny": "warn",
"noConsoleLog": "warn"
}
}
}
}Clean Up Old Files
Once the migration is validated, remove the old files:
# Remove ESLint files
rm -f .eslintrc .eslintrc.js .eslintrc.json .eslintrc.yml .eslintignore
# Remove Prettier files
rm -f .prettierrc .prettierrc.js .prettierrc.json .prettierrc.yml .prettierignore
# Uninstall dependencies
npm uninstall eslint prettier eslint-config-prettier \
eslint-plugin-react eslint-plugin-react-hooks \
@typescript-eslint/eslint-plugin @typescript-eslint/parser \
eslint-config-nextUpdate npm Scripts
{
"scripts": {
"lint": "biome check .",
"lint:fix": "biome check --write .",
"format": "biome format --write ."
}
}Step 5: Import Organization
Biome automatically sorts your imports according to clear conventions. Enable this feature:
{
"organizeImports": {
"enabled": true
}
}Before / After
// Before — unordered imports
import { useState, useEffect } from "react";
import axios from "axios";
import styles from "./page.module.css";
import { Button } from "@/components/ui/button";
import type { User } from "@/types";
import { z } from "zod";
import Link from "next/link";// After — automatically sorted
import { useEffect, useState } from "react";
import Link from "next/link";
import axios from "axios";
import { z } from "zod";
import type { User } from "@/types";
import { Button } from "@/components/ui/button";
import styles from "./page.module.css";Biome groups imports in this order:
- Native Node.js modules (
node:fs,node:path) - External packages (
react,next,zod) - Internal imports (alias
@/, relative paths) - Type imports (
type { ... })
The biome check --write command applies sorting alongside linting and formatting.
Step 6: VS Code Integration
Install the Extension
Search for "Biome" in the VS Code marketplace or install from the terminal:
code --install-extension biomejs.biomeConfigure VS Code
Add these settings in .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"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
}
}Disable the ESLint and Prettier extensions to avoid conflicts. Biome replaces both.
Editor Features
With the Biome extension, you get:
- Format on save: code is automatically formatted on every save
- Real-time diagnostics: linting errors appear directly in the editor
- Quick fixes:
Ctrl+.suggests automatic corrections - Import sorting: imports are reorganized on save
- Refactorings: renaming, extraction, and other code actions
Step 7: CI/CD Integration
GitHub Actions
Create .github/workflows/lint.yml:
name: Lint & Format
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
biome:
name: Biome Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome
run: biome ci .The biome ci command is optimized for CI pipelines: it does not write files, produces a diagnostics report, and returns a non-zero exit code if issues are found.
GitLab CI
Add to .gitlab-ci.yml:
biome:
stage: validate
image: node:20-slim
before_script:
- npm ci
script:
- npx @biomejs/biome ci .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"Pre-commit Hook with Husky
# Install Husky
npm install --save-dev husky
npx husky init
# Add pre-commit hook
echo "npx @biomejs/biome check --staged --no-errors-on-unmatched" > .husky/pre-commitThe --staged flag only checks staged files, making the hook nearly instant.
Step 8: Advanced Configuration
Per-Directory Overrides
Biome allows different configurations based on directories or file patterns:
{
"overrides": [
{
"include": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off"
}
}
}
},
{
"include": ["scripts/**"],
"linter": {
"rules": {
"suspicious": {
"noConsoleLog": "off"
}
}
}
},
{
"include": ["**/*.config.ts", "**/*.config.js"],
"formatter": {
"lineWidth": 120
}
}
]
}Ignoring Files
{
"files": {
"ignore": [
"node_modules",
".next",
"dist",
"build",
"coverage",
"*.min.js",
"generated/**"
]
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
}
}With "useIgnoreFile": true, Biome automatically respects your .gitignore.
Monorepo Configuration
For a monorepo, place biome.json at the root and use extends in packages:
my-monorepo/
├── biome.json # Base configuration
├── packages/
│ ├── web/
│ │ └── biome.json # Extends root config
│ ├── api/
│ │ └── biome.json # Extends root config
│ └── shared/
│ └── biome.json # Extends root config
// packages/web/biome.json
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"extends": ["../../biome.json"],
"linter": {
"rules": {
"a11y": {
"recommended": true
}
}
}
}Step 9: Complete Configuration for Next.js
Here is a complete, production-ready biome.json configuration for Next.js:
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"ignore": [
".next/**",
"node_modules/**",
"public/**",
".contentlayer/**"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"organizeImports": {
"enabled": true
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always",
"bracketSpacing": true
}
},
"json": {
"formatter": {
"trailingCommas": "none"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedVariables": "error",
"noUnusedImports": "error",
"useExhaustiveDependencies": "warn"
},
"style": {
"useConst": "error",
"useImportType": "error",
"noNonNullAssertion": "warn"
},
"suspicious": {
"noExplicitAny": "warn",
"noConsoleLog": "warn"
},
"nursery": {
"useSortedClasses": {
"level": "warn",
"options": {
"attributes": ["className"],
"functions": ["clsx", "cn", "cva"]
}
}
}
}
},
"overrides": [
{
"include": ["**/*.test.ts", "**/*.test.tsx"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off",
"noConsoleLog": "off"
}
}
}
}
]
}Step 10: Benchmark — ESLint + Prettier vs Biome
Let us measure the performance difference on a real project. Create a benchmark script:
#!/bin/bash
# benchmark.sh
echo "=== Benchmark: 500 TypeScript files ==="
echo ""
# Create test files
mkdir -p bench-files
for i in $(seq 1 500); do
cat > "bench-files/file-$i.tsx" << 'CONTENT'
import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { z } from "zod";
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(150),
});
type FormData = z.infer<typeof schema>;
export function UserForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
const [data, setData] = useState<FormData>({ name: "", email: "", age: 0 });
const [errors, setErrors] = useState<string[]>([]);
const validate = useCallback(() => {
const result = schema.safeParse(data);
if (!result.success) {
setErrors(result.error.errors.map((e) => e.message));
return false;
}
setErrors([]);
return true;
}, [data]);
useEffect(() => {
validate();
}, [validate]);
return (
<form
onSubmit={(e) => {
e.preventDefault();
if (validate()) onSubmit(data);
}}
>
<input
type="text"
value={data.name}
onChange={(e) => setData({ ...data, name: e.target.value })}
/>
<input
type="email"
value={data.email}
onChange={(e) => setData({ ...data, email: e.target.value })}
/>
<Link href="/users">Back</Link>
{errors.map((err) => (
<p key={err}>{err}</p>
))}
</form>
);
}
CONTENT
done
echo "--- ESLint + Prettier ---"
time npx eslint bench-files/ 2>/dev/null
time npx prettier --check bench-files/ 2>/dev/null
echo ""
echo "--- Biome ---"
time npx @biomejs/biome check bench-files/
# Cleanup
rm -rf bench-filesTypical Results
| Tool | 500 files | 1000 files | 5000 files |
|---|---|---|---|
| ESLint | ~8s | ~18s | ~95s |
| Prettier | ~3s | ~7s | ~35s |
| ESLint + Prettier | ~11s | ~25s | ~130s |
| Biome | ~0.4s | ~0.8s | ~3.5s |
Biome is approximately 25 to 35 times faster thanks to its Rust architecture and parallel parsing.
Troubleshooting
Common Issues
Formatting differs from Prettier
Biome aims for Prettier compatibility but some edge cases differ. If a specific difference blocks you:
{
"formatter": {
"attributePosition": "multiline"
}
}Conflict with Prettier extension in VS Code
Disable the Prettier extension or limit it to languages not supported by Biome:
{
"prettier.disableLanguages": [
"javascript",
"typescript",
"javascriptreact",
"typescriptreact",
"json"
]
}Custom ESLint rules not migrated
Some rules specific to third-party ESLint plugins are not yet supported by Biome. Check the compatibility matrix on the official site to verify support for your rules.
"File too large" error
By default, Biome ignores files larger than 1 MB. Adjust if needed:
{
"files": {
"maxSize": 2097152
}
}Next Steps
Now that Biome is set up in your project:
- Explore nursery rules: new rules are added with each release, like
useSortedClassesfor sorting Tailwind classes - Configure Git hooks so every commit is automatically checked
- Add CSS support: Biome now supports CSS/SCSS linting and formatting (experimental)
- Try
biome search: structural search in your code with GritQL patterns - Contribute: Biome is open source and welcomes contributions on GitHub
Conclusion
Biome represents a paradigm shift in JavaScript tooling. By replacing ESLint and Prettier with a single Rust-powered tool, you get:
- 25 to 35x faster speed on linting and formatting
- Simplified configuration with a single
biome.jsonfile - Zero conflicts between linter and formatter
- Clearer diagnostics with integrated fix suggestions
- A unified ecosystem expanding to CSS, GraphQL, and soon HTML
The migration from ESLint + Prettier is progressive and assisted by automatic migration tools. In 2026, Biome has become the default choice for new projects and a recommended upgrade for existing ones.
Discuss Your Project with Us
We're here to help with your web development needs. Schedule a call to discuss your project and how we can assist you.
Let's find the best solutions for your needs.
Related Articles

Building an Autonomous AI Agent with Agentic RAG and Next.js
Learn how to build an AI agent that autonomously decides when and how to retrieve information from vector databases. A comprehensive hands-on guide using Vercel AI SDK and Next.js with executable examples.

Building AI Agents from Scratch with TypeScript: Master the ReAct Pattern Using the Vercel AI SDK
Learn how to build AI agents from the ground up using TypeScript. This tutorial covers the ReAct pattern, tool calling, multi-step reasoning, and production-ready agent loops with the Vercel AI SDK.

Better Auth with Next.js 15: The Complete Authentication Guide for 2026
Learn how to implement full-featured authentication in Next.js 15 using Better Auth. This tutorial covers email/password, OAuth, sessions, middleware protection, and role-based access control.