Linting is like having a smart assistant that reads your code and helps you:
- β Find bugs before they cause problems
- β Follow best practices and coding standards
- β Keep code consistent across your project
- β Learn better coding patterns automatically
Think of it like spell-check for code - but much more powerful!
Biome is a modern, super-fast linter and formatter that helps you write better JavaScript and TypeScript code.
Your biome.json file is organized into these main sections:
π biome.json
βββ π§ Basic Setup (schema, vcs, files)
βββ π¨ Formatter (how code looks)
βββ π‘οΈ Linter Rules (what code quality rules to enforce)
βββ π JavaScript Settings (global variables)
βββ π JSON Settings (JSON parsing rules)
βββ π€ Assist (automatic code improvements)
βββ π― Overrides (special rules for specific files)
"$schema": "https://biomejs.dev/schemas/latest/schema.json"What it does: Enables autocomplete and validation in your code editor Beginner tip: Always keep this - it makes editing the config much easier!
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
}What it does:
enabled: Turns on git integrationclientKind: Tells Biome you're using gituseIgnoreFile: Respects your.gitignorefile (won't check ignored files)defaultBranch: Your main git branch name
Why it matters: Biome will automatically ignore files in your .gitignore, making it faster and more relevant
"files": {
"includes": ["**/*.{js,mjs,ts,json,jsonc}"],
"maxSize": 1048576,
"ignoreUnknown": true
}What it does:
includes: Only check these file types (JavaScript, TypeScript, JSON)maxSize: Skip files larger than 1MB (prevents slowdowns)ignoreUnknown: Don't show warnings for file types Biome doesn't understand
Beginner tip: The **/* pattern means "look in all folders and subfolders"
"formatter": {
"lineWidth": 80,
"indentStyle": "tab",
"indentWidth": 2,
"lineEnding": "lf"
}What it does:
lineWidth: Maximum 80 characters per line (classic standard)indentStyle: Use tabs for indentation (better for accessibility)indentWidth: 2 spaces worth of indentationlineEnding: Unix-style line endings (LF) for cross-platform consistency
Why these choices?:
- 80 characters: Works on all screen sizes, forces concise code
- Tabs: Users can set their preferred width in their editor
- LF endings: Prevents git issues between Windows/Mac/Linux
Your linter rules are organized into categories. Here's what each category does:
Purpose: Make your web apps usable by everyone, including people with disabilities
"a11y": {
"recommended": true // Enable all recommended accessibility rules
}What it catches: Missing alt text, improper ARIA usage, keyboard navigation issues Why important: Makes your apps inclusive and often legally required
Purpose: Prevent overly complex code that's hard to understand
"complexity": {
"noExtraBooleanCast": "error", // Don't use Boolean(x) when !!x works
"noUselessCatch": "error" // Don't catch errors just to rethrow them
}Example of what gets caught:
// β Bad - extra boolean cast
if (Boolean(someValue)) { }
// β
Good - direct boolean check
if (someValue) { }Purpose: Catch actual bugs and programming errors
Key rules explained:
noConstAssign: Can't reassign const variablesnoUndeclaredVariables: Must declare variables before usingnoUnreachable: Code after return statements is unreachablenoUnusedImports: Remove imports you're not using (NEW!)noUnusedFunctionParameters: Warn about unused function parameters (NEW!)noChildrenProp: Prevent React children prop misuse (NEW!)useIsNan: UseNumber.isNaN()instead ofx === NaN
Example:
// β This will error - can't reassign const
const name = "John";
name = "Jane"; // ERROR!
// β
Good - use let for reassignable variables
let name = "John";
name = "Jane"; // OK!Purpose: Prevent code that runs slowly
"performance": {
"noAccumulatingSpread": "warn", // Don't use spread in loops
"noDelete": "error" // Don't use delete operator
}Example:
// β Bad - slow spreading in loop
let result = [];
for (let item of items) {
result = [...result, item]; // Gets slower with each iteration!
}
// β
Good - use push instead
let result = [];
for (let item of items) {
result.push(item); // Fast!
}Purpose: Prevent security vulnerabilities
"security": {
"noGlobalEval": "error" // Never use eval() - it's dangerous!
}Purpose: Keep code consistent and readable
Important ones:
useConst: Useconstwhen values don't changeuseTemplate: Use template literals instead of string concatenationuseConsistentArrayType: Usestring[]instead ofArray<string>useForOf: Use modernfor...ofloops instead of traditional for loops (NEW!)useImportType: Use TypeScript type-only imports when possible (NEW!)
Examples:
// β Style issues
let name = "John"; // Should be const
let msg = "Hello " + name; // Should use template
let items: Array<string> = []; // Inconsistent array type
// β
Good style
const name = "John";
const msg = `Hello ${name}`;
const items: string[] = [];Purpose: Catch code that looks wrong or dangerous
Key highlights:
noConsole: Warns about console.log (should be removed in production)noDebugger: No debugger statements in productionnoDoubleEquals: Use===instead of==noExplicitAny: Avoid TypeScript'sanytypenoFocusedTests: Prevent.only()in test files (NEW!)noSkippedTests: Warn about.skip()in test files (NEW!)
Examples:
// β Suspicious patterns
if (x == y) { } // Should be ===
let data: any = {}; // Should have proper type
console.log("debug"); // Should be removed before production
// β
Better patterns
if (x === y) { }
let data: User = {};
// Use proper logging library insteadPurpose: Newest experimental rules (cutting-edge best practices)
"nursery": {
"useExplicitType": "warn", // Encourage explicit return types
"useNamingConvention": "warn" // Enforce naming standards
}"javascript": {
"globals": ["console", "process", "Buffer", "__dirname", "__filename", "Bun"],
"formatter": {
"quoteStyle": "single",
"semicolons": "always",
"trailingCommas": "es5"
}
}Global Variables:
- Tells Biome these global variables are OK to use
- Prevents "undefined variable" errors for runtime-specific globals
- Your globals: Node.js + Bun runtime variables
JavaScript Formatter (NEW!):
quoteStyle: Use single quotes instead of double quotessemicolons: Always use semicolons (safer than omitting)trailingCommas: Add trailing commas for ES5 compatibility (better git diffs)
"json": {
"parser": {
"allowComments": true // Allow // comments in JSON files
}
}What it does: Allows comments in JSON files (normally not allowed) Useful for: Configuration files that benefit from explanations
"assist": {
"actions": {
"source": {
"organizeImports": "on" // Auto-sort import statements
}
}
}What it does: Automatically organizes your import statements Example: Sorts imports alphabetically and groups them logically
Before:
import { useState } from 'react';
import axios from 'axios';
import { Button } from './Button';
import React from 'react';After auto-organize:
import React, { useState } from 'react';
import axios from 'axios';
import { Button } from './Button';Your config has smart overrides for different types of files:
{
"includes": ["*.test.{js,ts}", "*.spec.{js,ts}", "**/__tests__/**"],
"linter": {
"rules": {
"suspicious": { "noConsole": "off" }, // console.log OK in tests
"style": { "noImplicitBoolean": "off" } // Relaxed boolean rules
}
}
}Why: Test files often need console.log for debugging and have different patterns
{
"includes": ["*.config.{js,ts,mjs}", "vite.config.*"],
"linter": {
"rules": {
"style": { "noDefaultExport": "off" }, // Config files often need default exports
"suspicious": { "noConsole": "off" } // Console OK for build logs
}
}
}Why: Configuration files often need default exports and logging
{
"includes": ["package.json"],
"json": {
"parser": { "allowComments": false } // package.json can't have comments
}
}Why: Real package.json files don't support comments (npm requirement)
Your config uses three severity levels:
| Level | What it means | Example |
|---|---|---|
"error" |
β Must fix - blocks builds | Syntax errors, bugs |
"warn" |
Style issues, code smells | |
"off" |
π Ignored - rule disabled | Allowed in specific contexts |
# Check and fix all issues
bun run check
# Only check formatting
bun run format:check
# Only check linting
bun run lint:check
# Run in CI/CD (no changes, just report)
bun run check:ciindex.ts:1:1 lint/suspicious/noConsole FIXABLE βββββββββββ
β Don't use console.
> 1 β console.log("Hello via Bun!");
β ^^^^^^^^^^^
Reading this:
index.ts:1:1= File name, line 1, column 1lint/suspicious/noConsole= Rule category/nameFIXABLE= Biome can auto-fix thisβ= Warning level (not an error)
- Begin with warnings, make them errors later
- Focus on one rule category at a time
- Don't just turn off rules that seem annoying
- Learn why the rule exists first
- Let Biome fix what it can automatically
- Review the changes to learn patterns
- Install Biome extension for VS Code/your editor
- Enable format-on-save for automatic formatting
- Share this config with your team
- Use the same rules across all projects
"style": {
"existingRule": "error",
"newRule": "warn" // Add here
}"suspicious": {
"annoying-rule": "off" // Disable completely
}"correctness": {
"someRule": "warn" // Change from "error" to "warn"
}- Performance: Your config is optimized for speed with
maxSizelimits - Modern Features: Nursery rules give you cutting-edge best practices
- Context-Aware: Overrides make rules smart about file types
- Minimal: No redundant default values = faster parsing
- Biome Docs: https://biomejs.dev/
- Rule Explorer: https://biomejs.dev/linter/rules/
- VS Code Extension: Search "Biome" in extensions
- Practice: Run
bun run checkfrequently to learn patterns