CI 파이프라인에서 린트 단계가 45초 걸릴 때는 아무도 신경 안 썼다. 빌드가 3분이었으니까. 그런데 Vite 8과 Turbopack 덕에 빌드가 40초로 줄어버리니, 린트 45초가 갑자기 병목으로 눈에 들어왔다. Biome를 도입하고 그 45초가 0.8초가 됐다. 문제는 .eslintrc.json이 아직 프로젝트 루트에 남아있다는 거다.
56배 빠르다는 숫자의 체감
Biome v2.3은 Rust로 작성된 올인원 린터/포매터다. ESLint가 하는 일과 Prettier가 하는 일을 하나의 바이너리로 합쳤다. npm 의존성 두 개(+ 플러그인 열다섯 개)가 @biomejs/biome 하나로 줄어든다.
성능 차이를 숫자로 보면 이렇다.
| 작업 | ESLint + Prettier | Biome v2.3 | 배수 |
|---|---|---|---|
| 10,000 파일 린트 | 45.2초 | 0.8초 | 56x |
| 10,000 파일 포맷 | 12.1초 | 0.3초 | 40x |
| 콜드 스타트 | ~3초 | ~0.1초 | 30x |
로컬 개발에서 체감되는 건 콜드 스타트다. VSCode에서 ESLint 확장이 로드되는 그 3초간의 빨간줄 없는 세상 — 이 도구에서는 그게 없다. 에디터 열면 즉시 동작한다. save-on-format이 걸려있을 때 Prettier가 500ms 버벅이던 것도 사라진다. 0.3초에 만 개 파일을 포맷하는 도구가 파일 하나 저장에 걸릴 리가 없다.
올해 초 v2.3에서 추가된 type-aware linting이 판도를 바꿨다. 기존에는 TypeScript의 타입 정보가 필요한 룰 — no-floating-promises, no-misused-promises 같은 것 — 은 이 Rust 기반 도구가 건드리지 못했다. "타입 정보 필요한 룰은 ESLint에서만 가능하다"가 2025년까지의 정설이었다. 이제 자체 추론 엔진으로 tsc 없이도 타입 기반 검사를 돌린다. Vercel이 스폰서한 이 기능 덕에 그간의 "완전 대체는 아직 이르다" 논의가 크게 바뀌었다.
GritQL 기반 커스텀 플러그인도 안정화됐다. ESLint 플러그인을 직접 만들어본 사람은 알겠지만, AST visitor 패턴으로 룰 하나 작성하는 데 반나절이 걸렸다. GritQL은 패턴 매칭 쿼리 언어라서 대부분의 커스텀 룰을 10줄 이내로 표현할 수 있다. 현재 내장 423개 룰은 ESLint 코어 + 주요 플러그인의 약 80%를 커버한다.
근데 .eslintrc를 못 지우는 이유
eslint-plugin-react-hooks. 딱 이것 하나 때문이다.
React의 Hooks 규칙 — 조건부 호출 금지, 의존성 배열 검증 — 은 프론트엔드 프로젝트에서 사실상 필수 린트다. Biome가 기본적인 React 패턴은 검사하지만, Hooks 의존성 배열의 완전한 정적 분석은 아직 지원하지 않는다. useEffect 안에서 stale closure를 만들어내는 그 실수, CI에서 잡아주는 게 이 플러그인인데 동등한 대체재가 없다.
Next.js를 쓴다면 @next/eslint-plugin-next도 마찬가지다. Image 컴포넌트 최적화 패턴, 라우팅 규칙 검증 같은 프레임워크 특화 룰은 해당 프레임워크 팀이 직접 유지보수해야 의미가 있다. Biome 로드맵에 2026년 하반기 Hooks 규칙 동등성이 예고돼 있지만, react-hooks 수준의 완성도까지는 아직 시간이 필요하다.
하이브리드는 생각보다 깔끔하다
지금 시점에 가장 현실적인 셋업은 Biome + ESLint 하이브리드다. 결론부터 보자.
// biome.json — 포매팅 + 린트 전담
{
"formatter": { "enabled": true },
"linter": { "enabled": true },
"javascript": {
"formatter": { "semicolons": "asNeeded" }
}
}
// .eslintrc.json — 이 도구가 못 하는 것만 남긴다
{
"plugins": ["react-hooks", "@next/next"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
마이그레이션 자체는 명령어 두 줄이다.
npx @biomejs/biome migrate eslint --write
npx @biomejs/biome migrate prettier --write
기존 설정 파일을 읽어서 biome.json으로 변환해준다. 이후 ESLint에는 커버 못 하는 플러그인 룰만 남기고, Prettier는 완전히 제거한다. devDependencies에서 prettier, eslint-config-prettier, eslint-plugin-prettier, @typescript-eslint/parser가 사라지는 package.json diff를 보면 기분이 묘하게 좋다. 의존성 트리가 눈에 띄게 가벼워진다.
새 프로젝트라면 고민할 것도 없고
React Hooks를 안 쓰는 프로젝트 — Vue, Svelte, 순수 TypeScript 라이브러리 — 라면 단독으로 충분하다. 비슷한 포지션에 Oxlint(OXC 프로젝트)도 있지만, 포매터를 내장하지 않아서 여전히 Prettier와 조합해야 한다. "린트 + 포맷 = 바이너리 하나"라는 DX를 원하면 현재로선 선택지가 하나뿐이다.
React + Next.js 신규 프로젝트라도 Biome-first로 가되, ESLint는 react-hooks 룰 두 줄만 남기는 하이브리드를 권한다. .prettierrc 파일이 프로젝트에서 영원히 사라진다는 것만으로도 충분한 동기 부여가 된다.
로드맵대로라면 올해 말에 Hooks 규칙 동등성이 달성된다. 그때 npm uninstall eslint를 치는 날이 진짜 해방일이다. 그 전까지는 .eslintrc.json 파일 위에 // TODO: Biome가 react-hooks 지원하면 삭제 주석이나 달아두자.