Biome으로 ESLint·Prettier 옮기기: migrate 명령 결과, 옮겨지지 않는 규칙, unsafe 수정 검토

예제 프로젝트에 Biome 2.5를 실제로 적용하며 확인한 변환 결과와 diff

Biome 마이그레이션 실전 썸네일

핵심 요약

  1. biome migrate eslint/prettier는 legacy·flat 설정을 읽어 옮기고, 옮기지 못한 규칙을 목록으로 알려 준다.
  2. 예제에서 7개 규칙 중 max-depth가 "아직 구현되지 않음"으로 남았고, 생성된 설정은 vcs 연동이 꺼져 있었다.
  3. check --write는 safe 수정만 적용하며, unsafe 수정은 --unsafe를 줘야 적용되고 의미가 바뀔 수 있다.
  4. unsafe 수정은 console.log 줄 삭제, == → === 변경처럼 동작을 바꿀 수 있으므로 규칙별 커밋으로 나눠 검토한다.

린트는 ESLint, 포맷은 Prettier로 나눠 쓰면 설정 파일이 흩어지고, 에디터 저장 시 두 도구가 따로 돌며, CI도 두 번 실행된다. Biome은 포맷터와 린터를 한 실행 파일로 합친 도구이고, 기존 설정을 옮기는 migrate 명령을 제공한다. 이 글은 ESLint flat config와 Prettier 설정을 가진 예제 프로젝트에 Biome 2.5.15를 실제로 적용하며 확인한 결과를 정리한다. 아래 출력은 모두 2026-10-11에 Windows에서 실행한 결과다.

얻는 것과 잃는 것

구분내용
얻는 것설정 파일 하나(biome.json), 저장 시 한 번 실행, CI 단계 하나
잃는 것ESLint 플러그인 생태계 일부. 문서상 플러그인 규칙은 마이그레이션 후 biome.json에서 직접 설정해야 하고, 대응 규칙이 없을 수 있다
판단 기준타입 정보를 쓰는 규칙이나 프레임워크 전용 플러그인에 의존도가 높으면 포맷부터 옮기고 린트는 병행한다

마이그레이션 순서

  1. 별도 브랜치에서 @biomejs/biome을 개발 의존성으로 설치하고 biome init으로 설정 파일을 만든다.
  2. biome migrate eslint --write, biome migrate prettier --write로 기존 설정을 옮긴다.
  3. 출력에 나온 "옮기지 못한 규칙" 목록마다 대응 규칙을 찾을지, ESLint에 남길지, 버릴지 정한다.
  4. biome ci로 수정 없이 결과를 확인하고, 포맷 커밋과 린트 수정 커밋을 나눠 적용한다.
  5. 에디터의 ESLint·Prettier 저장 시 실행을 끄고, CI 명령을 biome ci로 바꾼다.

예제 프로젝트의 기존 설정

export default [
  {
    files: ['src/**/*.js'],
    rules: {
      'no-unused-vars': 'error',
      eqeqeq: 'error',
      'no-var': 'error',
      'prefer-const': 'error',
      'no-console': 'warn',
      'no-param-reassign': 'error',
      'max-depth': ['error', 3],
    },
  },
]
{ "semi": false, "singleQuote": true, "printWidth": 100, "trailingComma": "all" }
var DISCOUNT = 0.1;
export function total(items, coupon) {
  let sum = 0
  for (let i = 0; i < items.length; i++) { sum += items[i].price * items[i].qty }
  if (coupon == "WELCOME") { sum = sum * (1 - DISCOUNT) }
  const unused = 42;
  console.log("total", sum)
  return sum
}

migrate 명령 결과

문서에 따르면 ESLint 마이그레이션은 legacy와 flat 설정을 모두 읽고 extends를 따라가며, 기존 biome.json의 린터 설정을 교체하고 프리셋을 none으로 둔 뒤 옮긴 규칙만 켠다. 기본값으로는 ESLint 규칙과 동일한 규칙만 옮기고, 영감을 받은 수준의 규칙까지 옮기려면 --include-inspired를 붙인다.

$ npx biome migrate eslint --write
i Unsupported rules (0 incompatible with formatter, 0 made obsolete by the formatter, 0 covered by a formatter option, 1 not yet implemented, 0 unknown source):

i These rules have not yet been implemented:

- max-depth

i Migration results:
- ...\biome.json: configuration successfully migrated.

7개 규칙 중 6개가 옮겨졌고 max-depth는 "아직 구현되지 않음"으로 남았다. 이 목록이 사람이 직접 판단해야 할 규칙이다. 생성된 설정의 린터 부분은 다음과 같다.

"linter": {
  "enabled": true,
  "rules": {
    "preset": "none",
    "correctness": { "noUnusedVariables": "error" },
    "style": { "noParameterAssign": "error", "useConst": "error" },
    "suspicious": { "noConsole": "warn", "noDoubleEquals": "error", "noVar": "error" }
  },
  "includes": ["src/**/*.js"]
},
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }

Prettier 마이그레이션은 semi: false가 "semicolons": "asNeeded"로, singleQuote가 "quoteStyle": "single"로, printWidth: 100이 "lineWidth": 100으로 옮겨졌다. 한 가지 놓치기 쉬운 부분은 vcs.enabled: false다. 문서에 따르면 ESLint와 Prettier는 기본으로 .gitignore 항목을 건너뛰지만 Biome은 VCS 연동을 켰을 때만 그렇게 한다. 그대로 두면 빌드 산출물까지 검사 대상이 될 수 있으므로 "vcs": { "enabled": true, "useIgnoreFile": true }로 바꾼다.

safe 수정과 unsafe 수정은 결과가 다르다

biome ci로 먼저 수정 없이 결과를 확인하면 7개 오류와 1개 경고가 나온다. 그다음 check --write로 수정을 적용했다.

$ npx biome check --write .
src\order.js:11:3 lint/suspicious/noConsole  FIXABLE
  ! Don't use console.
src\order.js:7:14 lint/suspicious/noDoubleEquals  FIXABLE
  × Using == may be unsafe if you are relying on type coercion.
src\order.js:1:1 lint/suspicious/noVar  FIXABLE
  × Use let or const instead of var.
src\order.js:10:9 lint/correctness/noUnusedVariables  FIXABLE
  × This variable unused is unused.
Checked 5 files in 9ms. Fixed 4 files.
Found 3 errors.
Found 1 warning.

"Fixed 4 files"라고 나오지만 실제로 바뀐 것은 포맷(세미콜론, 따옴표, 줄바꿈)뿐이고, FIXABLE로 표시된 린트 문제는 그대로 남았다. Biome 문서는 safe 수정은 코드의 의미를 바꾸지 않아 검토 없이 적용할 수 있고, unsafe 수정은 의미를 바꿀 수 있어 직접 검토하라고 설명한다. unsafe 수정은 --write --unsafe를 함께 줘야 적용된다.

$ npx biome check --write --unsafe .   # 적용 후 src/order.js
const DISCOUNT = 0.1
export function total(items, coupon) {
  let sum = 0
  for (let i = 0; i < items.length; i++) {
    sum += items[i].price * items[i].qty
  }
  if (coupon === 'WELCOME') {
    sum = sum * (1 - DISCOUNT)
  }
  const _unused = 42
  return sum
}

unsafe 수정 결과를 하나씩 보면 왜 검토가 필요한지 드러난다.

규칙unsafe 수정 내용검토 포인트
noVarvar → const재할당이 없으니 안전하다
noDoubleEquals== → ===coupon이 숫자나 다른 타입으로 들어오는 호출이 있으면 동작이 바뀐다
noUnusedVariablesunused → _unused로 이름만 변경변수를 지운 것이 아니라 "의도적으로 안 쓴다"는 표시를 붙였다. 정말 필요 없는지 사람이 판단한다
noConsoleconsole.log 줄 삭제로그를 다른 곳에서 수집하고 있었다면 운영 로그가 사라진다

대량의 기존 코드에 처음 적용할 때는 포맷과 safe 수정만 한 커밋으로, unsafe 수정은 규칙별로 별도 커밋으로 나눠 리뷰한다. 수정 후에는 테스트를 반드시 돌린다. 모든 수정을 끝낸 뒤 biome ci는 "No fixes applied."와 함께 종료 코드 0을 냈다.

에디터·CI·커밋 훅 전환

  • 에디터에서 Biome 확장을 기본 포맷터로 지정하고, ESLint·Prettier 확장의 저장 시 실행을 끈다. 두 도구가 동시에 저장 훅을 잡으면 서로 다르게 포맷해 파일이 계속 바뀐다.
  • CI에서는 수정 없이 검사만 하는 biome ci를 쓴다.
  • lint-staged를 쓴다면 ESLint·Prettier 명령을 biome check --write로 바꾼다. 커밋 전 검사 구성은 ESLint·Prettier·tsc 커밋 전 자동화를 참고한다.
  • 되돌릴 때를 대비해 기존 설정 파일은 한동안 남겨 두고, README와 PR 템플릿의 명령을 함께 바꾼다.

AI 코딩 도구가 만든 코드에 이 검사를 자동으로 붙이는 방법은 Claude Code 훅으로 자동 검증 붙이기에서 다룬다.

초보자가 자주 실수하는 포인트

  • migrate 출력의 "옮기지 못한 규칙" 목록을 확인하지 않는다.
  • vcs 연동을 켜지 않아 .gitignore 대상 파일까지 검사한다.
  • "Fixed N files" 메시지만 보고 린트 문제가 모두 고쳐졌다고 생각한다.
  • --unsafe 결과를 검토 없이 한 커밋에 섞는다.

체크리스트

  • 별도 브랜치에서 migrate eslint·prettier를 실행했다.
  • 옮기지 못한 규칙마다 처리 방침을 정했다.
  • vcs 연동과 ignore 설정을 확인했다.
  • 포맷·safe 수정과 unsafe 수정을 별도 커밋으로 나눴다.
  • CI 명령을 biome ci로 바꾸고 에디터 저장 훅 충돌을 정리했다.

자주 묻는 질문

ESLint와 Biome을 같이 써도 되나요?

가능합니다. 포맷은 Biome, 타입 정보가 필요한 규칙이나 플러그인 규칙은 ESLint처럼 역할을 나누고, 같은 규칙이 양쪽에 중복 정의되지 않게 합니다.

--include-inspired는 언제 쓰나요?

기본값은 ESLint와 동일한 규칙만 옮깁니다. 비슷하지만 동작이 완전히 같지 않은 규칙까지 옮기려면 이 옵션을 붙이는데, 문서상 일부 옵션은 구현되지 않았거나 의도적으로 다를 수 있으므로 옮긴 뒤 결과를 확인합니다.

참고 자료 · 검증 기준

위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.

이 글은 위 참고 자료를 바탕으로 정리했으며, 내용은 운영 과정에서 순차적으로 보완될 수 있습니다.