린팅이 62배 빨라지면 개발 습관이 달라진다 — ESLint에서 Oxlint로, Vite 프로젝트 마이그레이션 경험
린터가 느리다고 느낀 적 있으신가요? 저도 그랬습니다. 중간 규모 React 프로젝트에서 pnpm lint를 돌릴 때마다 30초 가까이 기다리는 게 당연한 줄 알았는데, Oxlint로 바꾸고 나서 499ms가 나오는 걸 보고 처음엔 "설정이 잘못된 거 아닌가?" 의심했을 정도입니다. 공식 벤치마크 수치가 31초 vs 499ms이니 약 62배 차이인데, 실제로 경험해보면 그 숫자가 코드 작성 흐름에 얼마나 큰 영향을 주는지 실감하게 됩니다.
린팅이란 코드를 작성하면서 잠재적인 버그나 스타일 문제를 자동으로 감지해주는 것입니다. ESLint가 오랫동안 JavaScript/TypeScript 생태계의 표준이었는데, 요즘 Rust로 작성된 Oxlint가 그 자리를 빠르게 위협하고 있습니다. 속도뿐만 아니라 CI 린팅 비용 절감, 설정 유지보수 부담 감소도 실무에서 체감하는 이유입니다.
이 글에서는 기존 Vite + ESLint 프로젝트에서 Oxlint로 전환하는 방법을, 점진적 병행 운용부터 완전 교체, 그리고 Vite+ 통합 툴체인 미리 보기까지 단계별로 살펴봅니다. 이 글을 읽고 나면 자신의 프로젝트 상황에 맞는 마이그레이션 전략을 선택할 수 있습니다.
핵심 개념
Oxlint는 왜 이렇게 빠를까
처음엔 그냥 "Rust로 짜면 빠르다더라" 정도로만 알고 있었는데, 실제로 차이를 보고 나서 이유가 궁금해졌습니다. ESLint는 기본적으로 단일 스레드 모델로 설계되어 있어서, 규칙이 많아질수록 선형적으로 느려지는 구조입니다. 반면 Oxlint는 OXC(Oxidation Compiler) 프로젝트의 일부로 Rust로 작성되어 있고, 파일을 병렬로 처리하기 때문에 파일이 많을수록 효과가 배가됩니다.
OXC(Oxidation Compiler) — JavaScript/TypeScript 툴링을 Rust로 재구현하는 오픈소스 프로젝트. 파서, 린터(Oxlint), 번들러(Rolldown), 포매터(Oxfmt)가 동일한 코어 엔진을 공유합니다.
속도 차이가 실감이 안 된다면 이 수치를 보면 됩니다. M2 Max 기준 공식 벤치마크에서 ESLint가 31초 걸리는 작업을 Oxlint는 499ms에 처리합니다. 2026년 현재 695개 이상의 규칙이 eslint-plugin-react, typescript-eslint 등 주요 플러그인의 핵심 규칙 상당 부분을 커버하고 있어서, 많은 프로젝트에서 규칙 손실 없이 속도만 올리는 시나리오가 현실적으로 가능해졌습니다.
Vite 생태계에서 Oxlint의 위치
2026년 초, Vite 제작사인 VoidZero가 Vite+(vp CLI)를 공개 알파로 출시했습니다. Vite, Vitest, Oxlint, Oxfmt, Rolldown, tsdown을 단일 바이너리로 묶은 통합 툴체인입니다. Cloudflare의 VoidZero 인수로 이 생태계에 기업 투자도 가속화되고 있습니다.
| 도구 | 역할 | 상태 |
|---|---|---|
| Oxlint | 린터 (ESLint 대체) | v1.0 Stable |
| Rolldown | 번들러 (Vite 8+ 기본) | Stable |
| Oxfmt | 포매터 (Prettier 대체) | 개발 중 |
| Vite+ | 통합 CLI | Alpha |
지금 당장 적용하고 싶다면 vite-plugin-oxlint로 기존 Vite 프로젝트에 바로 붙일 수 있고, 더 과감하게 가고 싶다면 Vite+로의 전체 이전도 선택지입니다. 이 글에서는 두 경로를 모두 다룹니다.
@oxlint/migrate와 eslint-plugin-oxlint의 차이
저도 처음 마이그레이션을 준비할 때 이 두 패키지가 헷갈렸는데, 역할이 완전히 다릅니다.
| 패키지 | 역할 | 언제 사용 |
|---|---|---|
@oxlint/migrate |
ESLint 설정 → .oxlintrc.json 자동 생성 CLI |
ESLint를 완전히 걷어낼 때 |
eslint-plugin-oxlint |
Oxlint가 커버하는 규칙을 ESLint에서 비활성화 | ESLint와 병행 운용할 때 |
핵심 차이 —
@oxlint/migrate는 ESLint 설정을 Oxlint 설정으로 변환하는 도구이고,eslint-plugin-oxlint는 두 린터를 동시에 쓸 때 중복 검사를 방지하는 브릿지입니다. 둘 다 설치할 필요는 없습니다.
실전 적용
예시 1: vite-plugin-oxlint로 5분 만에 통합하기
가장 빠른 방법입니다. ESLint를 당장 제거하지 않아도 되고, 기존 빌드 파이프라인에 Oxlint를 얹는 것부터 시작할 수 있습니다. 저는 이 방법으로 처음 도입해봤는데, 설정 파일 없이도 바로 오류를 잡아줘서 진입 장벽이 생각보다 낮았습니다.
pnpm add -D vite-plugin-oxlint// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import oxlintPlugin from 'vite-plugin-oxlint'
export default defineConfig({
plugins: [
react(),
oxlintPlugin({
deny: ['correctness', 'perf'], // 이 카테고리 규칙 위반 시 빌드 실패
warn: ['suspicious'], // 빌드는 통과하되 경고 출력
allow: ['debugger'], // 규칙 이름 기준으로 비활성화
}),
],
})allow 옵션에는 규칙 이름('debugger')과 카테고리('style') 둘 다 넣을 수 있습니다. 카테고리는 correctness, perf, suspicious, style, pedantic 중 하나를 사용하면 됩니다.
| 옵션 | 설명 |
|---|---|
deny |
해당 카테고리 규칙 위반 시 빌드 실패로 처리 |
warn |
빌드는 통과하되 터미널에 경고 출력 |
allow |
특정 규칙(이름) 또는 카테고리 비활성화 |
이 상태에서 pnpm dev나 pnpm build를 실행하면 린팅이 함께 돌아갑니다. .oxlintrc.json이 없어도 기본값만으로 즉시 오류를 잡아주기 때문에, 설정 파일 없이 시작해서 필요할 때 추가하는 접근도 충분히 실용적입니다.
예시 2: @oxlint/migrate로 ESLint 설정 자동 변환하기
ESLint flat config(v9 방식)를 사용 중이라면 자동 변환이 됩니다. v8 레거시 설정(.eslintrc.*)을 쓰고 있다면 먼저 flat config로 변환이 필요한데, 이 순서를 지켜야 @oxlint/migrate가 제대로 읽을 수 있습니다.
# ESLint v8 레거시 설정이라면 먼저 flat config로 변환
npx @eslint/migrate-config .eslintrc.js
# flat config 기준으로 .oxlintrc.json 자동 생성
npx @oxlint/migrate실행하고 나면 이런 형태의 .oxlintrc.json이 생성됩니다.
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}주의 —
"typeAware": true옵션은 알파 기능입니다.options섹션에 추가하면no-floating-promises같은 타입 기반 규칙이 활성화되지만, 알파 특성상 CI 환경에서는 별도 검증을 권장합니다.
변환 후 반드시 터미널 출력에서 "unsupported rules" 로그를 확인해보는 게 중요합니다. Oxlint가 아직 지원하지 않는 규칙은 자동으로 건너뛰고 어떤 규칙이 누락됐는지 목록으로 알려줍니다. 흔히 마주치는 상황 중 하나가 plugin:react/recommended 같은 프리셋이 변환되지 않는 경우인데, Oxlint는 현재 ESLint 방식의 프리셋을 직접 지원하지 않습니다. 이 경우 누락 규칙 목록을 확인하고 필요한 규칙을 rules 섹션에 직접 추가하면 됩니다.
변환이 잘 됐는지 확인하고 싶다면 이렇게 돌려볼 수 있습니다.
# 변환된 설정으로 전체 린팅 결과 확인
pnpm oxlint .
# 특정 파일만 확인
pnpm oxlint src/components/MyComponent.tsx예시 3: ESLint와 병행 운용하는 점진적 마이그레이션
실무에서 가장 현실적인 시나리오입니다. 팀에서 사용하는 커스텀 ESLint 플러그인이 있거나, Oxlint가 아직 지원하지 않는 규칙이 있을 때 유용합니다.
pnpm add -D eslint-plugin-oxlint// eslint.config.mjs
import oxlint from 'eslint-plugin-oxlint'
import reactPlugin from 'eslint-plugin-react'
export default [
{
plugins: { react: reactPlugin },
rules: {
'react/prop-types': 'error',
'no-unused-vars': 'error',
},
},
// Oxlint가 커버하는 규칙을 ESLint에서 자동으로 비활성화
// → 두 린터가 같은 규칙을 중복 검사하지 않음
oxlint.configs['flat/recommended'],
]{
"scripts": {
"lint": "oxlint . && eslint ."
}
}Oxlint가 먼저 전체 파일을 빠르게 훑고, ESLint는 Oxlint가 커버하지 못하는 규칙만 처리하는 방식입니다. 솔직히 처음엔 "두 개를 동시에 돌리면 의미가 있나?" 싶었는데, Oxlint가 전체 파일의 95%를 0.5초에 처리하고 ESLint는 나머지 규칙만 확인하니 전체 속도가 눈에 띄게 빨라졌습니다.
예시 4: Vite+로 전체 툴체인 일원화하기
아직 알파 단계이지만, 미래 방향을 미리 살펴보고 싶다면 이렇게 접근할 수 있습니다. 저는 사이드 프로젝트에서 먼저 써봤는데, 설정 파일이 하나로 줄어드는 경험이 꽤 인상적이었습니다.
# 기존 프로젝트를 Vite+로 이전
npx vp migrate주의 — Vite+는 현재 알파 단계이고, 아래 설정 형식은 추후 변경될 가능성이 있습니다. 프로덕션 중요 프로젝트에는 신중하게 적용하는 것을 권장합니다.
// vite.config.ts (Vite+ 방식 — 린트 설정이 vite.config 안으로 통합됨)
export default {
lint: {
rules: {
'no-unused-vars': 'error',
'react/rules-of-hooks': 'error',
},
},
}그린필드 프로젝트나 사이드 프로젝트에서 먼저 경험해보시면, 통합 툴체인이 실제 개발 흐름에 어떤 변화를 주는지 체감할 수 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 압도적인 속도 | ESLint 대비 50~100배 빠름. M2 Max 기준 499ms vs 31초 |
| 멀티스레딩 | CPU 코어를 최대 활용, 파일이 많을수록 효과 배가 |
| 점진적 마이그레이션 가능 | ESLint와 공존 가능, 한 번에 다 바꾸지 않아도 됨 |
| 자동 마이그레이션 도구 | @oxlint/migrate로 flat config 자동 변환 |
| 제로 설정 시작 | .oxlintrc.json 없이도 기본값으로 즉시 동작 |
| 타입 인식 린팅 | typescript-eslint 대비 8~12배 빠른 타입 기반 규칙 (알파) |
| 낮은 CI 비용 | 린팅 시간이 줄면 CI 파이프라인 전체 실행 비용도 줄어듦 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 플러그인 프리셋 미지원 | plugin:react/recommended 같은 프리셋 직접 사용 불가 |
.oxlintrc.json에서 필요한 규칙을 rules 섹션에 개별 지정 |
| ESLint v8 레거시 설정 | .eslintrc.* 파일 자동 변환 불가 |
npx @eslint/migrate-config로 flat config 변환 후 마이그레이션 진행 |
| 일부 규칙 미지원 | 695개+ 지원이지만 전체 ESLint 생태계 커버 안 됨 | 마이그레이션 로그에서 누락 규칙 확인 후 eslint-plugin-oxlint로 병행 |
| JS Plugin은 알파 | ESLint 호환 JS 플러그인 지원이 2026년 3월 알파 | 사용 중인 플러그인의 Oxlint 호환 여부를 먼저 확인 |
| 타입 인식 린팅은 알파 | no-floating-promises 등 강력한 규칙은 알파 기능 활성화 필요 |
CI에서 간헐적 실패 가능성 있으므로 로컬에서 충분히 검증 후 활성화 권장 |
| Vite+는 알파 | vp CLI 전체가 아직 알파 단계 |
사이드 프로젝트에서 먼저 테스트 후 프로덕션 도입 검토 |
타입 인식 린팅(Type-Aware Linting) — TypeScript 타입 정보를 분석해 런타임 에러로 이어질 수 있는 패턴을 잡아내는 린팅 방식.
no-floating-promises(await 없이 Promise를 흘려보내는 패턴 감지) 같은 규칙이 대표적입니다. 알파 기능이라 CI에서 간헐적으로 실패하는 경우가 있어서, 처음엔 로컬에서만 켜두고 안정화를 확인한 뒤 CI에 활성화하는 것을 권장합니다.
실무에서 가장 흔한 실수
-
ESLint v8 설정을 그대로
@oxlint/migrate에 넘기기 —.eslintrc.js파일은@oxlint/migrate가 읽지 못합니다. 반드시npx @eslint/migrate-config로 flat config로 변환한 뒤 Oxlint 마이그레이션을 진행해야 합니다. -
누락 규칙 로그를 그냥 넘기기 —
@oxlint/migrate실행 후 "unsupported rules" 목록이 출력됩니다. 이걸 그냥 넘기면 기존 ESLint에서 잡아주던 규칙이 조용히 사라집니다. 누락된 규칙이 팀에 중요한 규칙인지 꼭 확인해보는 것을 권장합니다. -
eslint-plugin-oxlint없이 두 린터 동시 실행 — ESLint와 Oxlint를 함께 쓸 때 이 플러그인 없이 돌리면 같은 규칙을 두 번 검사하게 됩니다. 속도 이점이 반감될 뿐 아니라 에러 메시지가 중복으로 출력되어 혼란스러울 수 있습니다.
마치며
30초씩 기다리던 습관이 없어졌습니다. 이게 가장 솔직한 경험담입니다. 린팅이 빠르면 "저장 후 결과 확인"을 무의식적으로 자주 하게 되고, 그게 쌓이면 버그를 훨씬 일찍 발견하는 습관으로 이어집니다. 지금 당장 전체를 바꾸지 않아도 됩니다. 작은 것부터 시작해서 속도 차이를 직접 체감한 뒤 본격적인 마이그레이션을 결정해보시면 좋겠습니다.
지금 바로 시작할 수 있는 3단계:
-
플러그인으로 맛보기 —
pnpm add -D vite-plugin-oxlint를 설치하고vite.config.ts에 플러그인을 추가한 뒤,pnpm build또는pnpm dev실행 시 린팅 속도가 어떻게 달라지는지 확인해볼 수 있습니다. -
자동 마이그레이션 시도 — ESLint flat config를 사용 중이라면
npx @oxlint/migrate를 실행해.oxlintrc.json을 생성하고, 출력되는 "unsupported rules" 목록에서 팀에 중요한 규칙이 빠졌는지 검토해볼 수 있습니다. -
전략 선택 및 CI 적용 — 누락 규칙이 없거나 대체 가능하다면 ESLint를 완전히 제거하고, 아직 커버되지 않는 규칙이 있다면
eslint-plugin-oxlint로 병행 운용할 수 있습니다. 팀 전체가 혜택을 보려면 이 단계에서 CI 파이프라인의 린팅 스크립트도 함께 교체하는 것을 권장합니다.
참고 자료
- Oxlint 공식 문서
- Migrate from ESLint | Oxlint 공식 가이드
- vite-plugin-oxlint GitHub
- Announcing Vite+ Alpha | VoidZero
- Oxlint JS Plugins Alpha (2026-03-11)
- Announcing Oxlint Type-Aware Linting | VoidZero
- How to Gradually Migrate from ESLint to Oxlint
- Speed kills: It's time to retire ESLint and migrate to Oxlint | LogRocket
- Oxlint bench-linter GitHub (공식 벤치마크)
- I Tried Vite+ and Replaced My Entire Frontend Toolchain | DEV