ESLint + Prettier, 이제 Biome 하나로 끝

Rust 기반 올인원 린터/포매터로 갈아타기

Frontend
2025년 10월 6일

새 프로젝트를 셋팅할 때마다 ESLint와 Prettier 설정이 번거로웠습니다.
두 도구의 충돌 문제, 여러 개의 설정 파일, 느린 실행 속도까지.

회사에서 새 프로젝트 초기 설정을 맡을 때마다 이 부분에서 시간을 꽤 썼는데,
Biome을 도입한 뒤로 이런 고민이 많이 줄었습니다.

ESLint + Prettier의 문제점

설정 파일이 너무 많다

기존에 사용하던 설정 파일들입니다.

.eslintrc.js
.eslintignore
.prettierrc
.prettierignore

여기에 TypeScript를 쓰면 @typescript-eslint 플러그인 설정이 추가되고,
React를 쓰면 eslint-plugin-react, eslint-plugin-react-hooks가 추가됩니다.

.eslintrc.js
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'prettier', // 충돌 방지
  ],
  plugins: ['@typescript-eslint', 'react', 'react-hooks'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
    ecmaFeatures: { jsx: true },
  },
  // ... 생략
}

새 프로젝트마다 이걸 복붙하고, 버전 맞추고, 충돌 해결하는 데 시간이 꽤 들었습니다.
솔직히 매번 "이번엔 제발 한 번에 되자"라고 기도하면서 npm install을 눌렀습니다.

ESLint와 Prettier의 충돌

둘을 같이 쓰면 충돌이 발생합니다.
eslint-config-prettier로 ESLint의 포매팅 규칙을 끄고,
eslint-plugin-prettier로 Prettier를 ESLint 규칙으로 실행하거나.

처음 프론트엔드 시작했을 때 이 부분이 진짜 헷갈렸습니다.
"왜 두 개를 같이 쓰는데 또 다른 패키지가 필요하지?" 싶었는데,
알고 보니 원래 그런 거였습니다.

eslint-config-prettier는 충돌하는 ESLint 규칙을 비활성화하고,
eslint-plugin-prettier는 Prettier를 ESLint 규칙처럼 실행합니다.
둘은 다른 역할이지만 이름이 비슷해서 혼란스럽습니다.

느린 실행 속도

프로젝트가 커지면 ESLint 실행이 눈에 띄게 느려집니다.
저장할 때마다 2-3초씩 걸리면 개발 흐름이 끊깁니다.

예전 프로젝트에서 파일 500개 넘어가니까 린트 돌리는 데 10초 넘게 걸렸습니다.
커밋 전 훅에서 린트 돌리면 커피 한 모금 마시고 와야 했습니다.

Biome이란

Biome은 JavaScript/TypeScript 생태계를 위한 올인원 툴체인입니다.
Rust로 작성되어 빠르고, 린터와 포매터를 하나로 통합했습니다.

원래 Rome이라는 프로젝트였는데, 2023년에 Biome으로 포크됐습니다.
Rome 팀이 해체되면서 커뮤니티가 이어받은 케이스입니다.
현재는 활발하게 개발되고 있고, 2024년 JS Open Source Award도 수상했습니다.

주요 특징

  • 빠른 속도: Prettier 대비 25배 이상 빠름
  • 단일 설정 파일: biome.json 하나로 끝
  • 제로 설정: 기본값만으로도 충분히 사용 가능
  • Prettier 97% 호환: 기존 포매팅 스타일 유지 가능

설치 및 기본 설정

npm install --save-dev --save-exact @biomejs/biome
npx @biomejs/biome init

--save-exact를 쓰는 이유는 버전 간 호환성 때문입니다.
Biome은 마이너 버전에서도 규칙이 추가되거나 변경될 수 있어서,
정확한 버전을 고정하는 게 안전합니다.

처음에 이거 모르고 ^ 버전으로 설치했다가 팀원이랑 포매팅 결과가 달라서 당황했습니다.

init 명령을 실행하면 biome.json이 생성됩니다.

biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/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
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double"
    }
  }
}

이게 전부입니다. 플러그인 설치도 없고, extends 체인도 없습니다.
ESLint 설정하면서 "이게 왜 안 되지?" 하던 시간이 아까워지는 순간이었습니다.

내가 사용하는 설정

실제로 사용하는 설정입니다.

biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "files": {
    "ignoreUnknown": true,
    "ignore": ["node_modules", "dist", "build", ".next"]
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 80
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "style": {
        "noNonNullAssertion": "off"
      }
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "trailingCommas": "es5",
      "semicolons": "asNeeded"
    }
  }
}

포매터 설정

옵션설명
indentStylespace탭 대신 스페이스 사용
indentWidth22칸 들여쓰기
lineWidth80한 줄 최대 80자
quoteStylesingle작은따옴표 선호
semicolonsasNeeded필요할 때만 세미콜론

세미콜론 없는 스타일을 좋아하는데, 팀에서 반대 의견이 있으면 always로 바꾸기도 합니다.
이건 취향 영역이라 싸울 필요 없이 정하면 됩니다.

Prettier에서 마이그레이션할 때는 기존 설정과 최대한 비슷하게 맞추는 게 좋습니다.
코드 스타일이 갑자기 바뀌면 diff가 지저분해집니다.

VCS 연동

"vcs": {
  "enabled": true,
  "clientKind": "git",
  "useIgnoreFile": true
}

useIgnoreFile: true로 설정하면 .gitignore에 있는 파일을 자동으로 무시합니다.
별도의 .biomeignore 파일이 필요 없어서 편합니다.

린터 규칙 커스터마이징

recommended 규칙을 기본으로 사용하되, 필요한 것만 끕니다.

"rules": {
  "recommended": true,
  "style": {
    "noNonNullAssertion": "off"
  },
  "suspicious": {
    "noExplicitAny": "warn"
  }
}

저는 ! 단언은 허용하고, any는 경고만 표시하도록 설정했습니다.
any를 완전히 막으면 레거시 코드 마이그레이션할 때 힘들어서요.

CLI 사용법

# 린트 검사
npx biome lint ./src
 
# 포매팅
npx biome format ./src --write
 
# 린트 + 포매팅 한 번에
npx biome check ./src --write
 
# CI용 (수정 없이 검사만)
npx biome ci ./src

check 명령어가 가장 많이 쓰입니다.
린트와 포매팅을 한 번에 처리해서 편합니다.

package.json 스크립트

package.json
{
  "scripts": {
    "lint": "biome lint ./src",
    "lint:fix": "biome check ./src --write",
    "format": "biome format ./src --write",
    "check": "biome check ./src --write"
  }
}

VSCode 연동

Biome 확장을 설치하고, 설정을 추가합니다.

.vscode/settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  }
}

저장할 때 자동으로 포매팅되고, import 정렬도 됩니다.

기존에 Prettier 확장을 사용 중이라면 비활성화하거나,
workspace 설정에서 Biome을 기본 포매터로 지정해야 합니다.
둘 다 켜놓으면 저장할 때마다 포매팅이 두 번 돌아서 깜빡거립니다.

Husky + lint-staged와 함께 사용

커밋 전에 변경된 파일만 검사하도록 설정합니다.

package.json
{
  "lint-staged": {
    "*.{js,ts,jsx,tsx,json}": [
      "biome check --write --no-errors-on-unmatched"
    ]
  }
}

--no-errors-on-unmatched는 매칭되는 파일이 없어도 에러를 발생시키지 않습니다.
JSON 파일만 수정했을 때 에러가 나는 걸 방지합니다.

이거 없으면 README.md만 수정해도 에러가 나서 처음에 당황했습니다.

ESLint + Prettier에서 마이그레이션

기존 프로젝트를 Biome으로 옮기는 건 생각보다 수월합니다.
마이그레이션 명령어를 제공해서 대부분 자동으로 변환됩니다.

마이그레이션 명령어

# ESLint 설정 마이그레이션
npx @biomejs/biome migrate eslint --write
 
# Prettier 설정 마이그레이션
npx @biomejs/biome migrate prettier --write

.eslintrc.prettierrc 파일을 읽어서 biome.json으로 변환해줍니다.

마이그레이션 과정

실제로 마이그레이션하면서 했던 과정입니다.

# 1. Biome 설치
npm install --save-dev --save-exact @biomejs/biome
 
# 2. ESLint 설정 마이그레이션
npx @biomejs/biome migrate eslint --write
 
# 3. Prettier 설정 마이그레이션
npx @biomejs/biome migrate prettier --write
 
# 4. 전체 코드베이스 포매팅 (diff 한 번에 정리)
npx biome check ./src --write
 
# 5. 기존 패키지 제거
npm uninstall eslint prettier eslint-config-prettier \
  @typescript-eslint/eslint-plugin @typescript-eslint/parser \
  eslint-plugin-react eslint-plugin-react-hooks
 
# 6. 설정 파일 삭제
rm .eslintrc.js .eslintignore .prettierrc .prettierignore

마이그레이션 후 첫 커밋은 포매팅 변경만 따로 하는 게 좋습니다.
"chore: migrate to biome" 같은 커밋으로 분리하면 나중에 blame 추적이 편해집니다.

마이그레이션 결과 확인

마이그레이션 명령어를 실행하면 변환 결과가 출력됩니다.

Migration successful!

Converted rules:
  ✓ @typescript-eslint/no-unused-vars → noUnusedVariables
  ✓ @typescript-eslint/no-explicit-any → noExplicitAny
  ✓ react-hooks/rules-of-hooks → useHookAtTopLevel
  ✓ react-hooks/exhaustive-deps → useExhaustiveDependencies

Unsupported rules (manual review needed):
  ✗ import/order (partially supported via organizeImports)
  ✗ jsx-a11y/alt-text

지원되지 않는 규칙은 직접 확인해서 필요한지 판단해야 합니다.
대부분은 없어도 크게 문제없었습니다.

주의할 점

마이그레이션이 완벽하지는 않습니다.
몇 가지 수동으로 조정해야 할 부분이 있습니다.

1. import 순서

ESLint의 import/order 규칙처럼 세밀하게 제어하기 어렵습니다.
Biome의 organizeImports는 기본적인 정렬만 지원합니다.

// Biome 기본 정렬 결과
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Button } from '@/components/ui'
import { formatDate } from './utils'

그룹 사이에 빈 줄을 넣거나 하는 커스터마이징은 아직 안 됩니다.
개인적으로 이 부분이 좀 아쉬웠는데, 익숙해지니까 괜찮았습니다.

2. 커스텀 ESLint 플러그인

회사 내부에서 만든 커스텀 플러그인이 있다면 Biome에서 사용할 수 없습니다.
이 경우 ESLint와 병행하거나, 해당 규칙을 포기해야 합니다.

3. 포매팅 미세 차이

97% 호환이라는 건 3%는 다르다는 뜻입니다.
특히 긴 객체나 JSX 줄바꿈에서 Prettier와 미세하게 다를 수 있습니다.

// Prettier
<Component
  prop1="value1"
  prop2="value2"
/>
 
// Biome (가끔 다름)
<Component prop1="value1" prop2="value2" />

lineWidth 설정에 따라 다르게 동작하는 경우가 있어서,
마이그레이션 후 전체 diff를 한번 훑어보는 게 좋습니다.

지원되지 않는 규칙

Biome은 ESLint의 모든 규칙을 지원하지는 않습니다.
특히 플러그인 규칙 중 일부는 아직 미지원입니다.

  • eslint-plugin-import의 세부 규칙들
  • eslint-plugin-jsx-a11y의 일부 규칙
  • 각종 커스텀/서드파티 플러그인

필요한 규칙이 없다면 당분간 ESLint와 병행하거나,
해당 규칙 없이 운영 가능한지 검토가 필요합니다.

Biome 팀에서 지속적으로 규칙을 추가하고 있습니다.
규칙 목록에서 지원 현황을 확인할 수 있습니다.

속도 비교

실제로 프로젝트에서 측정한 결과입니다.
약 300개 파일 기준입니다.

도구소요 시간
ESLint + Prettier약 8초
Biome약 0.3초

체감상으로도 확연히 다릅니다.
저장할 때 기다리는 느낌이 없어졌습니다.

커밋 훅에서 lint-staged 돌릴 때도 거의 즉시 끝나서,
"어? 벌써 됐어?" 하는 느낌입니다.

헤맸던 부분들

도입하면서 몇 가지 막혔던 부분을 기록해둡니다.

1. VSCode에서 포매팅이 안 됨

Biome 확장 설치하고 설정도 했는데 저장해도 포매팅이 안 됐습니다.
알고 보니 Prettier 확장이 기본 포매터로 설정되어 있었습니다.

// 이렇게 명시적으로 지정해야 함
"[typescript]": {
  "editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
  "editor.defaultFormatter": "biomejs.biome"
}

2. JSON 파일 포매팅 에러

JSONC(주석 있는 JSON) 파일에서 에러가 났습니다.
tsconfig.json 같은 파일은 JSONC로 인식해야 하는데,
Biome이 일반 JSON으로 파싱하려고 해서 실패했습니다.

biome.json
{
  "json": {
    "parser": {
      "allowComments": true,
      "allowTrailingCommas": true
    }
  }
}

이 설정을 추가하면 해결됩니다.

3. 모노레포에서 설정 공유

모노레포 루트에 biome.json을 두면 하위 패키지에서도 적용됩니다.
패키지별로 다른 설정이 필요하면 해당 패키지에 biome.json을 추가하면 됩니다.

상위 설정을 상속받는 기능은 아직 없어서,
패키지별 설정이 필요하면 전체를 다시 작성해야 합니다.
이 부분은 좀 불편했습니다.

정리

Biome 도입 후 만족하고 있습니다.

좋은 점

  • 설정이 단순해졌습니다
  • 속도가 빨라졌습니다
  • ESLint/Prettier 충돌 문제가 사라졌습니다
  • 마이그레이션이 생각보다 수월합니다

고려할 점

  • 팀원들이 새 도구에 적응해야 합니다
  • 일부 ESLint 플러그인 규칙이 없을 수 있습니다
  • 모노레포 설정 상속이 아직 미지원입니다

새 프로젝트라면 Biome으로 시작하는 걸 추천합니다.
기존 프로젝트는 migrate 명령어로 한번 돌려보고, 변환 결과 확인 후 결정하면 됩니다.

개인적으로는 ESLint 설정 복붙하던 시절로 돌아가고 싶지 않습니다.

참고 자료

Tags:
BiomeESLintPrettierDX