Spectre
// PUBLISHED05.09.26
// TIME6 MINS
// TAGS
#BIOME#TYPESCRIPT#LINTING#FORMATTING
// AUTHOR
Spectre Command

G

o ships with gofmt. You run it, your code is formatted. No config, no plugins, no arguments about tabs vs spaces. The entire Go ecosystem shares one format because the tool makes the decision for you.

TypeScript doesn't have that. Or didn't. Biome is the closest thing to it: a linter and formatter in a single binary that runs fast enough that you stop noticing it's running, and opinionated enough that formatting debates stop happening.

This is the setup that gets you there.


Why ESLint + Prettier Is a Liability at Scale

The combination works. The combination is also a maintenance surface you're constantly managing.

ESLint has a plugin ecosystem of 2,000+ packages. Most projects use six to twelve of them. Each plugin is a separate dependency with its own release cycle, its own compatibility requirements, and its own bugs. eslint-config-airbnb pulls in seven packages. @typescript-eslint/eslint-plugin pulls in three. When ESLint releases a major version, you wait for all your plugins to catch up.

Prettier is separate configuration, separate package, separate invocation. If ESLint has formatting rules that conflict with Prettier and it does, by default you add eslint-config-prettier to disable them. One more package. One more thing to update.

The result is a lint/format setup that takes 30-60 seconds on a large project, breaks every few months when dependencies fall out of sync, and requires a senior engineer to diagnose when CI fails because @typescript-eslint/parser doesn't support the ESLint version you installed.

Biome collapses all of this into one binary, one config file, one command, and sub-second execution on most codebases.


Installation

Untitled
1bun add --dev @biomejs/biome

Or with npm:

Untitled
1npm install --save-dev @biomejs/biome

Initialize the config:

Untitled
1bunx biome init

This creates biome.json in the project root. That's the only config file you'll need.


The Config

Start here. This is the setup that works for production TypeScript projects without modification:

Untitled
1{
2 "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 "vcs": {
4 "enabled": true,
5 "clientKind": "git",
6 "useIgnoreFile": true
7 },
8 "files": {
9 "ignoreUnknown": false,
10 "ignore": ["dist", "node_modules", "coverage", ".next"]
11 },
12 "formatter": {
13 "enabled": true,
14 "formatWithErrors": false,
15 "indentStyle": "space",
16 "indentWidth": 2,
17 "lineEnding": "lf",
18 "lineWidth": 100,
19 "attributePosition": "auto"
20 },
21 "organizeImports": {
22 "enabled": true
23 },
24 "linter": {
25 "enabled": true,
26 "rules": {
27 "recommended": true,
28 "correctness": {
29 "noUnusedImports": "error",
30 "noUnusedVariables": "error",
31 "useExhaustiveDependencies": "warn"
32 },
33 "suspicious": {
34 "noExplicitAny": "error",
35 "noConsole": "warn"
36 },
37 "style": {
38 "noVar": "error",
39 "useConst": "error",
40 "useTemplate": "error"
41 },
42 "complexity": {
43 "noForEach": "warn"
44 }
45 }
46 },
47 "javascript": {
48 "formatter": {
49 "jsxSingleQuote": false,
50 "quoteStyle": "double",
51 "semicolons": "always",
52 "trailingCommas": "all"
53 },
54 "globals": []
55 },
56 "typescript": {
57 "formatter": {
58 "quoteStyle": "double"
59 }
60 }
61}

A few rules worth calling out.

"organizeImports": { "enabled": true } auto-sorts imports on format. No more import order lint errors. Biome handles it.

noExplicitAny: "error" will break codebases that have used any liberally. Don't set this to "warn" as a compromise that just means you accumulate warnings forever. Either set it to "error" and fix the underlying types, or don't enable it. Half-measures here are noise.

noConsole: "warn" catches console.log statements left in production code. Set it to "error" if you want CI to fail on them. We use "warn" because there are legitimate uses for console output in scripts and CLI tools.

useTemplate: "error" enforces template literals over string concatenation. `Hello ${name}` instead of "Hello " + name. Small thing, but consistent.


Running Biome

Untitled
1# Check everything (lint + format check, no changes)
2bunx biome check .
3
4# Fix everything that can be auto-fixed
5bunx biome check --write .
6
7# Format only
8bunx biome format --write .
9
10# Lint only
11bunx biome lint .

Add to package.json:

Untitled
1{
2 "scripts": {
3 "check": "biome check .",
4 "check:fix": "biome check --write .",
5 "format": "biome format --write ."
6 }
7}

CI should run biome check . without --write. If there are formatting violations, CI fails. Engineers fix locally with biome check --write . before pushing.


The Part Most Teams Get Wrong

They install Biome and keep ESLint running in parallel "just to be safe."

I understand the instinct. ESLint has rules you've relied on for years. Some of them feel important. But running both creates a situation where engineers never fully trust Biome, so the ESLint config never gets removed, and you end up with two tools providing overlapping feedback that occasionally conflicts.

Pick one. If you want Biome, commit to it. Audit which ESLint rules you actually care about, confirm Biome covers them (the migration guide at biomejs.dev maps ESLint rules to Biome equivalents), and delete the ESLint config.

The one legitimate exception: a rule your project genuinely needs that Biome doesn't have. In that case, keep ESLint for that specific rule only, with a minimal config that does nothing else. Don't keep the full ESLint setup.

The other mistake: using biome-ignore comments the same way teams used eslint-disable. One biome-ignore with a real explanation is fine. Thirty of them means you're configuring around the tool instead of using it.


Real-World Example: Linear's Tooling Philosophy

Linear the project management tool used by thousands of engineering teams has written publicly about their TypeScript setup. One consistent thread: they treat formatting and linting as non-negotiable infrastructure. The config is decided once, applied everywhere, and the only valid PR comment about formatting is "Biome should have caught this is it not running?"

The teams we've worked with that adopted this stance saw a specific change: code review comments shifted from formatting and style discussions to architecture and logic discussions. Not because engineers got more disciplined. Because the tool made the trivial decisions automatic.

A two-engineer team running ESLint + Prettier was spending roughly 90 minutes per week in PRs on formatting and import order. After switching to Biome with format-on-save in VSCode, that dropped to near zero. Not because they formatted better because the tool formatted for them on every save.


VSCode Setup

Install the official Biome extension: biomejs.biome.

Add to .vscode/settings.json:

Untitled
1{
2 "editor.defaultFormatter": "biomejs.biome",
3 "editor.formatOnSave": true,
4 "editor.codeActionsOnSave": {
5 "source.organizeImports.biome": "explicit",
6 "quickfix.biome": "explicit"
7 },
8 "[typescript]": {
9 "editor.defaultFormatter": "biomejs.biome"
10 },
11 "[typescriptreact]": {
12 "editor.defaultFormatter": "biomejs.biome"
13 },
14 "[javascript]": {
15 "editor.defaultFormatter": "biomejs.biome"
16 },
17 "[json]": {
18 "editor.defaultFormatter": "biomejs.biome"
19 }
20}

Commit .vscode/settings.json. Every engineer on the team gets the same format-on-save behavior without manual configuration.

Disable the Prettier VSCode extension for this project. They'll conflict, and Biome will win in CI while Prettier wins in the editor, which means code that looks right locally fails CI. Avoid the confusion.


FAQ

Q: Does Biome support JSX and React? A: Yes. JSX formatting and linting work out of the box. The useExhaustiveDependencies rule catches missing React hook dependencies, similar to eslint-plugin-react-hooks. No separate plugin installation required.

Q: Can Biome lint JSON and YAML files? A: JSON, yes. YAML is not supported as of Biome 1.9. If you have YAML linting requirements, you'll need a separate tool for those files specifically.

Q: How does Biome handle .env files and secrets? A: It doesn't that's not its job. Biome lints and formats source code. For secret detection in commits, use a separate tool like gitleaks or GitHub's built-in secret scanning. Don't expect Biome to catch hardcoded API keys.

Q: Is Biome stable enough for production CI? A: Yes. Biome 1.x has been in production at companies including Netlify and Astro's own codebase. The 1.x series follows semver breaking changes don't happen in minor releases. Pin to a specific minor version in package.json and update intentionally.

Q: What happens when Biome and tsconfig disagree on something? A: They operate on different layers and rarely conflict. Biome checks code style and correctness patterns. TypeScript checks types. The one overlap: both care about noUnusedLocals and import usage. If both are enabled, you'll see the error from both. That's redundant but not harmful; you can disable the TypeScript compiler option and let Biome handle it.


One config file. One command. No plugin dependency hell. The goal isn't to make your linter fancy it's to make it invisible so you can think about the actual code.

The full toolchain this fits into is in the TypeScript project setup guide if you're building from scratch, start there.

Internal Reference Logs: * * *

External Documentation:

// END_OF_LOGSPECTRE_SYSTEMS_V1

Is your current architecture slowing you down?

Stop guessing where the bottlenecks are. We partner with founders and CTOs to audit technical debt and execute zero-downtime system rewrites.

Book an Architecture Audit