Prettier + husky + lint-staged로 팀 코드 스타일 자동화하기
왜 코드 포매팅 자동화가 필요했는가
프로젝트를 진행하다 보면 코드 스타일에 대한 논쟁이 생기기 마련입니다. 탭이냐 스페이스냐, 세미콜론을 붙이냐 마냐, 작은따옴표냐 큰따옴표냐. 사소해 보이지만 코드 리뷰 때마다 이런 논쟁에 시간을 쓰고 있다는 생각이 들었습니다.
결국 필요한 것은 "누가 작성하든 동일한 스타일"을 강제하는 시스템이었습니다. 그래서 Prettier와 husky + lint-staged 조합을 도입하기로 했습니다.
Prettier vs ESLint: 역할이 다르다
처음에는 ESLint만 있으면 되지 않나 생각했는데, 둘은 역할이 명확히 다릅니다.
도구 | 역할 | 질문 |
|---|---|---|
ESLint | 코드 품질, 버그 탐지 | "이 코드가 올바른가?" |
Prettier | 코드 스타일, 포매팅 | "이 코드가 예쁜가?" |
ESLint는 잠재적 버그를 찾아주고, Prettier는 코드 스타일을 통일해줍니다. 둘은 보완 관계라는 것을 알게 되었습니다.
Prettier의 철학: Opinionated
Prettier가 흥미로웠던 점은 "Opinionated" 철학입니다. 설정 옵션이 의도적으로 제한되어 있습니다.
"tabs vs spaces 논쟁에 30분 쓰지 마세요. 그냥 Prettier가 정한 대로 쓰세요."
ESLint는 400개 이상의 규칙이 있지만, Prettier는 10개 남짓의 옵션만 제공합니다. 선택지가 적으니 논쟁도 없어집니다.
실제 설정 과정
1단계: Prettier 설치
pnpm add -D prettier prettier-plugin-tailwindcss -w모노레포 환경이라 -w 플래그로 루트에 설치했습니다. Tailwind CSS를 사용 중이라 클래스 정렬 플러그인도 함께 설치했습니다.
2단계: 설정 파일 생성
.prettierrc 파일을 프로젝트 루트에 생성했습니다.
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}기본값에서 변경한 것은 세 가지뿐입니다.
singleQuote: true- 작은따옴표 선호trailingComma: "es5"- ES5 호환printWidth: 100- 줄 길이 여유 있게
.prettierignore로 제외할 파일도 지정했습니다.
node_modules/
dist/
build/
.next/
pnpm-lock.yaml3단계: husky + lint-staged 설치
여기서 핵심은 "커밋할 때 자동으로 포매팅"입니다. 수동으로 prettier --write를 실행하는 것은 잊기 쉽습니다.
pnpm add -D husky lint-staged -w
npx husky init.husky/pre-commit 파일이 생성되는데, 내용을 다음과 같이 수정했습니다.
npx lint-staged그리고 package.json에 lint-staged 설정을 추가했습니다.
{
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,scss,md,html}": "prettier --write"
}
}4단계: 기존 코드 일괄 포매팅
새로운 커밋은 자동으로 포매팅되지만, 기존 코드는 한 번 전체 포매팅이 필요합니다.
npx prettier --write .수백 개의 파일이 변경되었습니다. 이런 대량 변경은 별도 커밋으로 분리하는 것이 좋습니다.
git blame 관리: .git-blame-ignore-revs
포매팅 커밋의 문제점이 있습니다. git blame으로 "이 코드 누가 작성했지?" 확인하면 모든 줄이 Prettier 커밋으로 나옵니다.
이를 해결하는 것이 .git-blame-ignore-revs 파일입니다.
# Prettier initial formatting (2026-01-10)
12fcd101478b99a327d96dd27abce74a9d1e2906이 파일을 커밋해두면 GitHub 웹 UI에서 자동으로 인식하고, blame 시 해당 커밋을 건너뜁니다.
로컬에서도 적용하려면 git 설정이 필요합니다.
git config blame.ignoreRevsFile .git-blame-ignore-revs작동 흐름 정리
코드 수정
↓
git add (staging)
↓
git commit
↓
husky가 pre-commit hook 실행
↓
lint-staged가 staged 파일에만 prettier --write 실행
↓
포매팅된 파일로 커밋 완료staged 파일만 처리하므로 빠르고, 강제성이 있어서 포매팅을 잊을 수 없습니다.
결론
이제 코드 스타일에 대해 논쟁할 필요가 없어졌습니다. 누가 작성하든 커밋하는 순간 동일한 스타일로 통일됩니다.
설정에 들인 시간은 30분 정도였는데, 앞으로 코드 리뷰에서 스타일 관련 논쟁에 쓸 시간을 생각하면 충분히 가치 있는 투자였다는 생각이 들었습니다.
관련 글
TypeScript verbatimModuleSyntax 마이그레이션 실전 가이드
TypeScript 5.0의 verbatimModuleSyntax 옵션을 모노레포 프로젝트에 적용하면서 배운 것들을 정리했습니다. 타입과 값의 import를 명확히 구분하는 것이 왜 중요한지, 그리고 실제 마이그레이션 과정에서 마주친 패턴들을 공유합니다.
Playwright E2E 테스트: 프론트엔드와 백엔드를 동시에 검증하는 실전 가이드
단위 테스트만으로는 실제 사용자 경험을 보장할 수 없다는 것을 깨달았습니다. Playwright E2E 테스트를 통해 프론트엔드와 백엔드를 동시에 구동하고, 실제 사용자 시나리오를 검증한 경험을 정리했습니다.
NestJS에서 Drizzle ORM을 선택한 이유: TypeORM, Prisma와의 비교
새로운 SaaS 모듈에 MySQL을 도입하면서 TypeORM, Prisma, Drizzle ORM을 비교했습니다. 각 ORM의 장단점과 Drizzle을 선택한 이유를 실제 코드 예시와 함께 정리했습니다.