<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rajat Kumar</title>
    <description>The latest articles on DEV Community by Rajat Kumar (@rjonmshka).</description>
    <link>https://dev.to/rjonmshka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F640205%2Fb66cd21c-aa32-4f89-ad18-9da14aff2b09.png</url>
      <title>DEV Community: Rajat Kumar</title>
      <link>https://dev.to/rjonmshka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rjonmshka"/>
    <language>en</language>
    <item>
      <title>The Two-Tool Problem: ESLint, Prettier, and the Case for a Unified Toolchain</title>
      <dc:creator>Rajat Kumar</dc:creator>
      <pubDate>Fri, 27 Mar 2026 22:52:41 +0000</pubDate>
      <link>https://dev.to/rjonmshka/the-two-tool-problem-eslint-prettier-and-the-case-for-a-unified-toolchain-3ene</link>
      <guid>https://dev.to/rjonmshka/the-two-tool-problem-eslint-prettier-and-the-case-for-a-unified-toolchain-3ene</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive · Tooling&lt;/strong&gt; · ~18 min read · ESLint · Prettier · Biome · TypeScript&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most JavaScript projects ship with both ESLint and Prettier without anyone really understanding &lt;em&gt;why&lt;/em&gt; two separate tools are needed — or why combining them into one is harder than it looks. This is the post that should have existed when I first set up a new project and stared at four config files doing roughly the same thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The right mental model, first&lt;/li&gt;
&lt;li&gt;ESLint: what it actually does under the hood&lt;/li&gt;
&lt;li&gt;Prettier: why it reprints instead of fixing&lt;/li&gt;
&lt;li&gt;The overlap zone: where things get messy&lt;/li&gt;
&lt;li&gt;Configuring both properly in 2026&lt;/li&gt;
&lt;li&gt;Why one tool couldn't do this for so long&lt;/li&gt;
&lt;li&gt;Biome: the serious attempt at unification&lt;/li&gt;
&lt;li&gt;A real comparison across a codebase&lt;/li&gt;
&lt;li&gt;What Biome still cannot do&lt;/li&gt;
&lt;li&gt;Verdict and migration strategy&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The right mental model, first
&lt;/h2&gt;

&lt;p&gt;The confusion starts because both tools ship as dev dependencies, both produce errors in your terminal, both integrate with your editor, and both run in CI. They touch the same files. So the reasonable assumption is that they're doing the same thing — just with different opinions about it.&lt;/p&gt;

&lt;p&gt;That assumption is wrong, and it's the root of every misconfiguration I've seen in the wild.&lt;/p&gt;

&lt;p&gt;The cleanest way to think about this: ESLint and Prettier are answering &lt;strong&gt;completely different questions&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;ESLint&lt;/th&gt;
&lt;th&gt;Prettier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core question&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Is this code correct and safe?&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Does this code look consistent?&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;Semantics &amp;amp; logic&lt;/td&gt;
&lt;td&gt;Aesthetics &amp;amp; layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operates on&lt;/td&gt;
&lt;td&gt;AST (abstract syntax tree)&lt;/td&gt;
&lt;td&gt;Raw text → reprinted output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can it catch bugs?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Errors &amp;amp; warnings with location&lt;/td&gt;
&lt;td&gt;Reformatted file (always)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-fixable?&lt;/td&gt;
&lt;td&gt;Partially — many rules aren't auto-fixable&lt;/td&gt;
&lt;td&gt;Always — the whole point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configurable?&lt;/td&gt;
&lt;td&gt;Deeply — write custom rules, use plugins&lt;/td&gt;
&lt;td&gt;Barely — intentionally opinionated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framework-aware?&lt;/td&gt;
&lt;td&gt;Yes, via plugins (React, Angular, Vue, etc.)&lt;/td&gt;
&lt;td&gt;Language-aware only, not framework-aware&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither is a subset of the other. They're orthogonal tools that happen to share a pipeline step. Internalizing that distinction makes every configuration decision obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  ESLint: what it actually does under the hood
&lt;/h2&gt;

&lt;p&gt;ESLint parses your source into an &lt;strong&gt;Abstract Syntax Tree&lt;/strong&gt; — a structured, in-memory representation where every piece of code becomes a typed node. A function call is a &lt;code&gt;CallExpression&lt;/code&gt; node. A variable declaration is a &lt;code&gt;VariableDeclaration&lt;/code&gt; node. ESLint then runs a traversal over this tree, calling registered rule handlers at each node type.&lt;/p&gt;

&lt;p&gt;This is why ESLint can catch things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A variable that's declared but never referenced (&lt;code&gt;no-unused-vars&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A promise-returning function whose return value is discarded (&lt;code&gt;no-floating-promises&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A React hook called inside a conditional (&lt;code&gt;react-hooks/rules-of-hooks&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Potential XSS via unsanitized &lt;code&gt;innerHTML&lt;/code&gt; assignment (&lt;code&gt;no-unsanitized&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;TypeScript type errors surfaced without running &lt;code&gt;tsc&lt;/code&gt; (&lt;code&gt;@typescript-eslint/no-unsafe-assignment&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;em&gt;semantic&lt;/em&gt; problems. The tool is reasoning about what your code &lt;em&gt;means&lt;/em&gt; and whether that meaning is sound — not what it looks like on screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ESLint sees this as: VariableDeclaration → VariableDeclarator → Identifier("result")&lt;/span&gt;
&lt;span class="c1"&gt;// It knows "result" is never used elsewhere in scope. Prettier has no concept of scope.&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// no-floating-promises: missing await&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// no-unused-vars: userData declared, never read&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ESLint also runs &lt;strong&gt;scope analysis&lt;/strong&gt; — it maintains a symbol table as it traverses, tracking where variables are defined and referenced. That's what makes rules like &lt;code&gt;no-shadow&lt;/code&gt; and &lt;code&gt;no-use-before-define&lt;/code&gt; possible. Prettier processes the same file as text and reprints it — it has no notion of a symbol table.&lt;/p&gt;

&lt;h3&gt;
  
  
  The plugin architecture that makes ESLint powerful
&lt;/h3&gt;

&lt;p&gt;ESLint's real strength is that rule-writing is a public API. A plugin is just a Node.js module that exports rules, each of which is a visitor object — a set of handlers keyed by AST node type. The React Hooks plugin, for example, registers a visitor on &lt;code&gt;CallExpression&lt;/code&gt; nodes, checks whether the callee looks like a hook, and then traverses the call stack to validate that hooks are only called from the top level of a function component.&lt;/p&gt;

&lt;p&gt;This is why the ecosystem is so rich. Any team can encode their own invariants — domain rules, API usage contracts, security constraints — and enforce them statically. That kind of extensibility is architecturally incompatible with what Prettier is trying to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prettier: why it reprints instead of fixing
&lt;/h2&gt;

&lt;p&gt;Prettier also parses your code into an AST — but then it does something fundamentally different with it. It &lt;strong&gt;throws away your original formatting entirely&lt;/strong&gt; and reprints the file from scratch using its own layout algorithm. Every run of Prettier on the same logical code produces the exact same output. This is called &lt;em&gt;idempotent formatting&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Prettier takes your code and reprints it from scratch by taking the line length into account, wrapping code when necessary."&lt;br&gt;
— Prettier docs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The example below shows what this means in practice. You can hand Prettier wildly different formatting of the same logical code, and it produces identical output every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (anything goes):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;     &lt;span class="nx"&gt;user&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rajat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;engineer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After Prettier (always the same output):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rajat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;engineer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prettier intentionally offers almost no configuration. You can set quote style, semicolons, tab width, trailing commas, and print width — that's essentially it. This is by design. The whole value proposition is that &lt;strong&gt;formatting ceases to be a decision&lt;/strong&gt;. No more code review comments about semicolons. No more team debates about line length. Prettier decides, everyone accepts it, and you move on to the actual work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Prettier's inflexibility is the feature. If it let you configure everything, teams would spend time configuring it — defeating the entire purpose of having a formatter in the first place. This is a conscious and correct tradeoff.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The overlap zone: where things get messy
&lt;/h2&gt;

&lt;p&gt;Here's where the confusion compounds. ESLint has always had &lt;em&gt;stylistic rules&lt;/em&gt; — rules about indentation, quote style, semicolons, spacing — that overlap with what Prettier manages. For years, linters were the only tool doing any formatting, so these rules existed for a reason. But once Prettier entered the picture, you now have two systems with conflicting opinions about the same bytes.&lt;/p&gt;

&lt;p&gt;A classic conflict: ESLint's &lt;code&gt;quotes&lt;/code&gt; rule set to &lt;code&gt;"double"&lt;/code&gt;, Prettier's &lt;code&gt;singleQuote: true&lt;/code&gt;. ESLint rewrites to double quotes, Prettier rewrites to single, they fight forever and your editor goes insane on every save.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The classic conflict — what developers actually experienced&lt;/span&gt;

&lt;span class="c"&gt;# ESLint (with quotes: ["error", "double"]) fixes this:&lt;/span&gt;
const name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Rajat'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   →   const name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Rajat"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Prettier (with singleQuote: true) then immediately fixes it back:&lt;/span&gt;
const name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Rajat"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   →   const name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Rajat'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Infinite loop. Editor goes to war with itself on every save.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The solution the community landed on is &lt;code&gt;eslint-config-prettier&lt;/code&gt; — a shareable ESLint config that disables all ESLint rules that conflict with Prettier. You put it last in your config array so it overrides anything that might fight Prettier. Think of it as a white flag: ESLint fully surrenders the formatting territory to Prettier.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; &lt;code&gt;eslint-plugin-prettier&lt;/code&gt; — which runs Prettier &lt;em&gt;as an ESLint rule&lt;/em&gt; — is a different beast and generally the wrong approach. It chains Prettier's output into ESLint's pipeline, creating double-parsing overhead, slower CI runs, and confusing error messages that blend linting and formatting in the same output. The ESLint team and typescript-eslint both recommend against it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Configuring both properly in 2026
&lt;/h2&gt;

&lt;p&gt;ESLint's flat config (&lt;code&gt;eslint.config.js&lt;/code&gt;) is now the default, with &lt;code&gt;defineConfig()&lt;/code&gt; from &lt;code&gt;eslint/config&lt;/code&gt; being the current recommended helper as of ESLint v9. The old &lt;code&gt;tseslint.config()&lt;/code&gt; wrapper is deprecated. Here's a well-structured setup for a TypeScript + React project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.js&lt;/span&gt;
&lt;span class="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;eslint&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@eslint/js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eslint/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tseslint&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;typescript-eslint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;reactHooks&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eslint-plugin-react-hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;prettierConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eslint-config-prettier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Base JS rules&lt;/span&gt;
  &lt;span class="nx"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. TypeScript rules (strict = recommended + extra correctness rules)&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;tseslint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strictTypeChecked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Type-aware rules require pointing at your tsconfig&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;parserOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;projectService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tsconfigRootDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. React Hooks rules&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reactHooks&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reactHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Your own overrides&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@typescript-eslint/no-floating-promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@typescript-eslint/consistent-type-imports&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-console&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// 6. Prettier LAST — disables all conflicting ESLint style rules.&lt;/span&gt;
  &lt;span class="c1"&gt;// This is a config object (not a plugin), so it just turns off rules.&lt;/span&gt;
  &lt;span class="nx"&gt;prettierConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.prettierrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"semi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"singleQuote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tabWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trailingComma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"printWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arrowParens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endOfLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lf"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For your &lt;code&gt;package.json&lt;/code&gt; scripts, run them separately — never chain Prettier through ESLint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="s2"&gt;"eslint . --max-warnings 0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s2"&gt;"eslint . --fix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format:check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"prettier --check ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CI:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(fast)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;full&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lint&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ci:quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s2"&gt;"prettier --check . &amp;amp;&amp;amp; eslint . --max-warnings 0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Run Prettier before ESLint in CI. Prettier is dramatically faster (formatting doesn't need to build a type-aware AST), so failing fast on a formatting issue saves the time of running the full ESLint pass on badly-formatted code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The config files you end up with
&lt;/h3&gt;

&lt;p&gt;In a typical TypeScript monorepo this expands to: &lt;code&gt;eslint.config.js&lt;/code&gt;, &lt;code&gt;.prettierrc&lt;/code&gt;, &lt;code&gt;.prettierignore&lt;/code&gt;, plus any workspace-level overrides. That's already 3–4 files, with dependencies like &lt;code&gt;@typescript-eslint/parser&lt;/code&gt;, &lt;code&gt;typescript-eslint&lt;/code&gt;, &lt;code&gt;eslint-config-prettier&lt;/code&gt;, and &lt;code&gt;prettier&lt;/code&gt; itself — each with their own peer dependency trees. For a bare &lt;code&gt;create-next-app&lt;/code&gt; this routinely pulls in 60–80 packages just to make linting and formatting work.&lt;/p&gt;

&lt;p&gt;This is not a deal-breaker. It's tooling; it's meant to be set up once. But it does mean there's real maintenance surface area — and it's the problem that Biome is trying to solve at the architectural level.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why one tool couldn't do this for so long
&lt;/h2&gt;

&lt;p&gt;The obvious follow-up question: why wasn't this solved years ago? If both tools parse an AST, why can't one tool do both passes?&lt;/p&gt;

&lt;p&gt;The answer is architectural, and it's worth understanding — because it also explains why Biome's design is genuinely novel rather than just marketing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatting and linting have opposite design requirements
&lt;/h3&gt;

&lt;p&gt;A good formatter must be &lt;strong&gt;deterministic and opinionated&lt;/strong&gt;. You want exactly one correct output for any given input. The moment you start making rules configurable, you invite teams to configure them differently, which reintroduces the formatting debates the tool was meant to eliminate. Prettier's intentional inflexibility is the product.&lt;/p&gt;

&lt;p&gt;A good linter must be &lt;strong&gt;extensible and configurable&lt;/strong&gt;. Every codebase has different invariants — a fintech product has security rules a portfolio site doesn't need, a React app has hook rules that an Angular app doesn't care about. You need a plugin system, per-file overrides, custom rule authoring. If you lock the linter down the way Prettier locks the formatter down, it becomes useless for most real projects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Core tension:&lt;/strong&gt; The qualities that make a great formatter (inflexible, opinionated, deterministic) are architecturally opposed to the qualities that make a great linter (extensible, configurable, context-sensitive). Building one tool that does both well requires solving this at the design level — not just merging two CLIs into one binary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The performance problem with naive combinations
&lt;/h3&gt;

&lt;p&gt;When you use &lt;code&gt;eslint-plugin-prettier&lt;/code&gt; (the approach that runs Prettier as an ESLint rule), you're forcing ESLint to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the file into an AST&lt;/li&gt;
&lt;li&gt;Run all linting rules&lt;/li&gt;
&lt;li&gt;Apply auto-fixes (including Prettier's formatting changes)&lt;/li&gt;
&lt;li&gt;Re-parse the now-modified file&lt;/li&gt;
&lt;li&gt;Run rules again to verify fixes didn't introduce new violations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On a project with type-aware TypeScript rules, each parse-and-lint cycle is already slow because it needs to construct a full type program. Running Prettier inside that cycle multiplies the pain. This is why, on large codebases, &lt;code&gt;eslint --fix&lt;/code&gt; with Prettier embedded can take 30–60 seconds — and why some teams disable type-aware rules entirely just to keep CI manageable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Biome: the serious attempt at unification
&lt;/h2&gt;

&lt;p&gt;Biome (originally forked from the Rome project) is the most credible attempt to build a unified toolchain from scratch. It's written in Rust, ships as a single binary, and takes a fundamentally different architectural approach that makes the one-tool dream actually tractable. As of early 2026, it's at &lt;strong&gt;v2.4&lt;/strong&gt; and has surpassed 15 million monthly downloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  The key architectural insight: parse once, use everywhere
&lt;/h3&gt;

&lt;p&gt;Biome parses your code into its own CST (Concrete Syntax Tree — preserves more information than an AST, including whitespace) &lt;strong&gt;exactly once&lt;/strong&gt;, then runs both the formatter and the linter over it in separate passes using the same in-memory tree. This eliminates the redundant parsing that makes chained tools slow.&lt;/p&gt;

&lt;p&gt;The formatter and linter are implemented as separate visitors over the same tree, so they don't interfere with each other's operation — but they also don't need to pay the parsing cost twice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;biome.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(v&lt;/span&gt;&lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://biomejs.dev/schemas/2.4.8/schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"organizeImports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"formatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"indentStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"indentWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lineWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lineEnding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lf"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"linter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recommended"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"correctness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"noUnusedVariables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"useExhaustiveDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"suspicious"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"noConsoleLog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"noExplicitAny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"useTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"noParameterAssign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"performance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"noDelete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"javascript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"formatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quoteStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"double"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"semicolons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"trailingCommas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"arrowParentheses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".next"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coverage"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"vcs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clientKind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"useIgnoreFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single file replaces: &lt;code&gt;eslint.config.js&lt;/code&gt;, &lt;code&gt;.prettierrc&lt;/code&gt;, &lt;code&gt;.prettierignore&lt;/code&gt;, and the coordination glue between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @biomejs/biome check &lt;span class="nb"&gt;.&lt;/span&gt;          &lt;span class="c"&gt;# lint + format check (reports only)&lt;/span&gt;
npx @biomejs/biome check &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;  &lt;span class="c"&gt;# lint + format + auto-fix&lt;/span&gt;
npx @biomejs/biome format &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="c"&gt;# format only&lt;/span&gt;
npx @biomejs/biome lint &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;   &lt;span class="c"&gt;# lint + auto-fixable rules only&lt;/span&gt;

&lt;span class="c"&gt;# Migration from an existing project:&lt;/span&gt;
npx @biomejs/biome migrate eslint &lt;span class="nt"&gt;--write&lt;/span&gt;   &lt;span class="c"&gt;# reads eslint.config.js / .eslintrc and ports rules&lt;/span&gt;
npx @biomejs/biome migrate prettier &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="c"&gt;# reads .prettierrc and ports options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error output that's actually useful
&lt;/h3&gt;

&lt;p&gt;One underrated improvement: Biome's error messages are designed to explain the &lt;em&gt;intent&lt;/em&gt; of a rule, not just flag the location. Compare:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ESLint:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✖  1 problem (1 error, 0 warnings)

src/utils.ts
  12:7  error  'result' is assigned a value but never used
        @typescript-eslint/no-unused-vars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Biome:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lint&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;correctness&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;noUnusedVariables&lt;/span&gt;

&lt;span class="err"&gt;✖&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;declared&lt;/span&gt; &lt;span class="nx"&gt;but&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="o"&gt;^^^^^^&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ℹ&lt;/span&gt; &lt;span class="nx"&gt;Unused&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;often&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;incomplete&lt;/span&gt; &lt;span class="nx"&gt;refactoring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagnostic gives you the rule category, a contextual code frame, and an explanatory note about &lt;em&gt;why&lt;/em&gt; the rule exists. It's a small thing that adds up fast when you're onboarding a team.&lt;/p&gt;




&lt;h2&gt;
  
  
  A real comparison across a codebase
&lt;/h2&gt;

&lt;p&gt;Speed benchmarks across the community are consistent. On a Next.js + TypeScript project with 300+ source files, the difference is stark. Note that these are relative numbers — exact timing depends on machine, file count, and rule complexity.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Time (312 files)&lt;/th&gt;
&lt;th&gt;Relative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ESLint + Prettier&lt;/td&gt;
&lt;td&gt;~28s&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Biome check&lt;/td&gt;
&lt;td&gt;~1.4s&lt;/td&gt;
&lt;td&gt;~20× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oxlint (lint only)&lt;/td&gt;
&lt;td&gt;~0.6s&lt;/td&gt;
&lt;td&gt;~50× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This matters for two practical reasons: CI pipeline time (where every second costs money and developer attention), and editor feedback latency (where slow linting breaks the tight write-see-error-fix loop that makes static analysis valuable in the first place).&lt;/p&gt;

&lt;h3&gt;
  
  
  Package weight comparison
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ESLint + Prettier setup for TypeScript + React&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    eslint @eslint/js typescript-eslint &lt;span class="se"&gt;\&lt;/span&gt;
    eslint-plugin-react-hooks &lt;span class="se"&gt;\&lt;/span&gt;
    eslint-config-prettier prettier

added 127 packages   &lt;span class="c"&gt;# config files: 3-4&lt;/span&gt;

&lt;span class="c"&gt;# ─────────────────────────────────────&lt;/span&gt;

&lt;span class="c"&gt;# Biome equivalent&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; @biomejs/biome

added 1 package      &lt;span class="c"&gt;# config files: 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The package count difference is somewhat misleading — those 127 packages are mostly shared transitive deps that other tools also use. But the config file consolidation is genuinely significant in large repos and monorepos, where config drift between workspaces is a real problem.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Biome still cannot do
&lt;/h2&gt;

&lt;p&gt;Biome is production-ready for most TypeScript/React/Next.js projects as of v2.4. But there are genuine gaps worth knowing before you migrate. This isn't a reason to avoid Biome — it's information to make an honest decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin ecosystem&lt;/strong&gt; — ESLint has a decade of community plugins: &lt;code&gt;eslint-plugin-testing-library&lt;/code&gt;, &lt;code&gt;eslint-plugin-security&lt;/code&gt;, &lt;code&gt;eslint-plugin-unicorn&lt;/code&gt;, and hundreds more. Biome's plugin story via GritQL (introduced in v2.0) is live and growing, but not yet at parity with ESLint's ecosystem depth. If you depend on specific plugins, check coverage first at &lt;a href="https://biomejs.dev/linter/json/rules/" rel="noopener noreferrer"&gt;biomejs.dev/linter/json/rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep type-aware rules&lt;/strong&gt; — &lt;code&gt;typescript-eslint&lt;/code&gt; in type-checked mode can run rules against your full type program, catching things like &lt;code&gt;no-unsafe-assignment&lt;/code&gt; across module boundaries. Biome's type inference engine (expanded in v2.4 to resolve &lt;code&gt;Record&amp;lt;K, V&amp;gt;&lt;/code&gt; types and improve &lt;code&gt;noFloatingPromises&lt;/code&gt; accuracy) is improving but not yet at full parity with typescript-eslint's depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language coverage&lt;/strong&gt; — Prettier formats Vue SFCs, SCSS, YAML, Markdown, GraphQL, and MDX. Biome v2.4 covers JS, TS, JSX, TSX, JSON, CSS, and GraphQL, with experimental support for Vue, Svelte, and Astro. SCSS support is in active development. If your project formats Markdown or YAML through Prettier, you'll need to keep it for those file types or use a separate tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Angular / Vue template linting&lt;/strong&gt; — &lt;code&gt;angular-eslint&lt;/code&gt; parses Angular templates with Angular's own compiler and runs template-aware rules. There's no Biome equivalent yet. Vue's &lt;code&gt;eslint-plugin-vue&lt;/code&gt; similarly operates on SFC templates. If you're on Angular or Vue and relying on template linting, keep ESLint for now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;eslint-disable&lt;/code&gt; migration&lt;/strong&gt; — Biome uses &lt;code&gt;biome-ignore&lt;/code&gt; comments, not &lt;code&gt;eslint-disable&lt;/code&gt;. During migration, Biome reveals how many suppression comments your codebase has accumulated — and they won't carry over automatically. This can surface a lot of latent tech debt at once. Factor that into your migration sprint planning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON-only config&lt;/strong&gt; — Biome's config is JSON (or JSONC). You can't write it in JavaScript, which means no dynamic config based on environment variables or computed values — a minor but real limitation for projects with complex per-environment rule sets.&lt;/p&gt;

&lt;h3&gt;
  
  
  The honest hybrid strategy
&lt;/h3&gt;

&lt;p&gt;For projects that need the ESLint plugin ecosystem — particularly Angular, Vue, or any project with deep security rule requirements — the pragmatic path is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Biome&lt;/strong&gt; for formatting and the core lint rules it covers (it's faster and cleaner)&lt;/li&gt;
&lt;li&gt;Keep &lt;strong&gt;ESLint&lt;/strong&gt; for the specific plugins that Biome doesn't have equivalents for yet&lt;/li&gt;
&lt;li&gt;Drop &lt;strong&gt;Prettier&lt;/strong&gt; entirely (Biome's formatter scores 97%+ Prettier compatibility and is significantly faster)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You still cut the config complexity roughly in half and get most of the performance benefit, without betting entirely on a newer tool for rules that matter to your project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verdict and migration strategy
&lt;/h2&gt;

&lt;p&gt;After digging through the architecture and the practical tradeoffs, here's where I land.&lt;/p&gt;

&lt;p&gt;The ESLint + Prettier split is &lt;em&gt;not&lt;/em&gt; redundancy — it's two genuinely different tools solving two genuinely different problems. Understanding the split makes you a better engineer regardless of which tools you use. But the configuration overhead and performance cost of running them as separate pipelines is a real problem, and Biome solves it at the right level — not by gluing tools together, but by redesigning the architecture.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New project&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Start with Biome. For TypeScript + React/Next.js, it covers 95%+ of what ESLint + Prettier gave you, at a fraction of the config cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Existing TS/React&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Migrate incrementally. Run &lt;code&gt;biome migrate eslint&lt;/code&gt; + &lt;code&gt;biome migrate prettier&lt;/code&gt;, validate, then remove the old configs over a sprint or two.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Angular project&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keep ESLint with &lt;code&gt;angular-eslint&lt;/code&gt;. Replace Prettier with Biome's formatter only. Evaluate full migration as Biome's Angular support matures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Heavy plugin deps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stay on ESLint for the rules you can't replace. Drop Prettier, use Biome as your formatter. You still win on the formatting side.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monorepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Biome's single-binary, single-config model is particularly compelling here. Config drift between workspaces is a common pain it directly solves.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bigger takeaway is conceptual. For years, "use ESLint and Prettier together" was tribal knowledge — you did it because everyone else did it, not because you understood the why. Understanding that ESLint reasons about &lt;em&gt;meaning&lt;/em&gt; and Prettier reasons about &lt;em&gt;appearance&lt;/em&gt; — and that these require fundamentally different architectures — is the insight that makes every tooling decision from here obvious.&lt;/p&gt;

&lt;p&gt;Biome is what happens when you start from that insight and build for it from day one, in Rust, with a unified AST and a shared parse tree. It's not perfect yet. But the architectural foundation is correct, and the trajectory — 15 million monthly downloads, experimental Vue/Svelte/Astro support, a functioning GritQL plugin system, and a clear 2026 roadmap — makes the direction unmistakable.&lt;/p&gt;




</description>
      <category>eslint</category>
      <category>prettier</category>
      <category>biome</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
