포맷을 지켜줘, 허스키! 🐾

이현성·2026년 5월 31일
post-thumbnail

초반에 Prettier 설정을 해두고 "잘 적용되고 있겠지" 싶었는데... 그런데 같은 팀원으로부터 파일 중에 Prettier가 적용되지 않은 부분이 있다는 이야기를 들었다.

아니 나는 lint 잡는 게 같이 해주는 건 줄 알았는데 따로 설정이나 검사가 더 필요하다는 걸 이때 알았다;

그래서 급하게 Prettier 자동 적용을 걸어놓긴 했는데, 팀원분 중 한 분이 감사하게도 Husky 설정을 해주셨다.

이참에 Prettier부터 Husky까지, 현재 프로젝트에 설정되어 있는 모습을 기반으로 하나씩 알아가보자.


Prettier

Prettier는 코드 포맷터다.

ESLint처럼 코드 품질이나 버그 가능성을 잡는 도구라기보다는, 코드의 모양을 정해진 규칙대로 다시 써주는 도구에 가깝다. 쉽게 말해 취향이라고 볼 수 있고.

예를 들면 이런 거

  • 들여쓰기
  • 줄바꿈
  • 쉼표 위치
  • 따옴표 스타일
  • 객체나 배열을 여러 줄로 펼칠지 여부
const user = { name: "stamply", role: "admin" };

이런 코드가 있을 때, 설정에 따라 줄바꿈이나 쉼표를 자동으로 정리해준다.


Prettier VS Lint

이 둘은 다음과 같은 차이가 있는데,

구분PrettierLint
관심사코드 모양코드 품질, 규칙, 잠재적 문제
대표 도구prettiereslint
하는 일들여쓰기, 줄바꿈, 쉼표 정리사용하지 않는 변수, 잘못된 Hook 사용, 규칙 위반
실행 결과보통 자동으로 고쳐줌문제를 알려주거나 일부 자동 수정
기준포맷팅 규칙코드 품질 규칙

예를 들어 이런 코드는 Prettier의 관심사다.

const event = { title: "popup", status: "open" };

Prettier는 이 코드를 보기 좋은 모양으로 정리한다.

const event = { title: "popup", status: "open" };

반면 이런 코드는 Lint의 관심사에 가깝다.

const unusedTitle = "Stamply";

변수를 만들어놓고 안쓸 때 "이 변수 진짜 필요한 거 맞아?" 하고 알려주는게 얘가 하는 일이다.

우리 프로젝트에서는 pnpm lint를 실행하면 eslint가 동작한다.

{
  "scripts": {
    "lint": "eslint"
  }
}

Prettier 실행 방법

Prettier를 설치만 해두면 사람이 직접 실행해야 한다. 한번도 한 적이 없었다..;

pnpm exec prettier --write .

이런 명령어로 돌릴 수 있다고 한다.


VSCode에서 저장할 때 Prettier 적용하기

이전에 VSCode에서 자동 저장 시 적용되는 설정을 한 기억이 있어 한번 적용해보자!

VSCode에서는 Prettier - Code formatter 확장을 설치하고 formatOnSave를 켜면 저장할 때마다 Prettier가 실행된다.

개인 VSCode 설정에 아래처럼 넣을 수 있다.

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "prettier.requireConfig": true
}

각 옵션은 이런 의미다.

옵션의미
editor.formatOnSave저장할 때 자동으로 포맷팅한다
editor.defaultFormatter기본 포맷터를 Prettier VSCode 확장으로 지정한다
prettier.requireConfigPrettier 설정 파일이 있는 프로젝트에서만 Prettier 실행

여기서 prettier.requireConfig를 켜두면 아무 프로젝트에서나 Prettier가 막 적용되는 일을 줄일 수 있다.

Stamply에는 .prettierrc가 있으므로 이 옵션을 켜도 Prettier가 정상 적용된다.

다만 현재 프로젝트에는 .vscode/settings.json이 없다.

즉, 팀 공통으로 "저장 시 Prettier 적용" 설정을 저장소에 강제해둔 상태는 아니다.

팀 전체에 같은 VSCode 설정을 공유하고 싶다면 아래 파일을 추가할 수 있겠다.

.vscode/settings.json

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "prettier.requireConfig": true
}

근데 이건 VSCode 한적이라 협업에 쓰기엔 좀.. 생각해보니 다들 어떤거 쓰는지 궁금하네.


Git Hook

Git Hook은 Git의 특정 이벤트 앞뒤에 자동으로 실행되는 스크립트다.

예를 들면 이런 타이밍에 끼어들 수 있다.

Hook실행 시점자주 쓰는 용도
pre-commit커밋이 만들어지기 전포맷팅, 린트, 테스트
commit-msg커밋 메시지를 받은 뒤커밋 메시지 규칙 검사
pre-pushpush 되기 전테스트, 빌드

이번 프로젝트에서 쓰는 건 pre-commit이다.

pre-commit은 커밋이 만들어지기 전에 실행된다. 여기서 스크립트가 실패하면 커밋도 중단된다.

그래서 포맷팅이나 린트를 커밋 전에 강제하기 좋다.

다만 Git Hook에는 한 가지 아쉬운 점이 있다.

기본 Git Hook 파일은 .git/hooks 아래에 들어가는데, .git 디렉터리는 보통 Git에 커밋되지 않는다.

즉, 내 컴퓨터에만 hook을 잘 만들어둬도 팀원 컴퓨터에는 자동으로 공유되지 않는다.

여기서 Husky가 등장한다.


Husky🐾

Husky는 Git Hook을 프로젝트 안에서 관리할 수 있게 도와주는 도구다.

원래 Git Hook은 .git/hooks에 직접 넣어야 하지만, Husky를 쓰면 .husky/pre-commit 같은 파일로 hook을 관리할 수 있다.

이 파일은 프로젝트에 커밋할 수 있으므로 팀원들도 같은 hook을 공유할 수 있다.

현재 Stamply 프로젝트에는 아래 hook이 있다.

# .husky/pre-commit
pnpm lint-staged

말 그대로 커밋 직전에 pnpm lint-staged를 실행한다.

그리고 package.json에는 이 설정을 넣는다.

{
  "scripts": {
    "prepare": "husky"
  }
}

pnpm install을 하면 prepare 스크립트를 통해 Husky hook이 설치된다.

정리하면 이렇다.

도구역할
Git HookGit 이벤트에 맞춰 스크립트를 실행하는 기능
HuskyGit Hook을 프로젝트에서 관리하고 설치해주는 도구
lint-stagedstaged 상태인 파일만 골라서 명령어를 실행하는 도구
Prettier파일을 정해진 스타일로 포맷팅하는 도구

lint-staged

lint-staged는 staged 파일을 대상으로 명령어를 실행한다.

여기서 staged 파일이란 git add로 커밋에 포함시키겠다고 올려둔 파일이다.

staged 파일만 보는 이유는 전체 프로젝트에 Prettier를 돌리면 내가 건드리지 않은 파일까지 대량으로 바뀔 수 있기 때문이다.

그러면 PR에서 실제 변경과 포맷 변경이 섞여서 어마어마한 파일이.. 여기까지.

그래서 커밋에 들어갈 파일만 대상으로 포맷팅하는 게 일반적이라고 한다.

{
  "lint-staged": {
    "**/*.{js,jsx,ts,tsx,css}": ["prettier --write"]
  }
}

의미는

대상 파일실행 명령어
js, jsx, ts, tsx, cssprettier --write

즉, 커밋에 포함된 JavaScript, TypeScript, CSS 파일만 Prettier로 자동 정리한다.


적용된 Prettier 설정

현재 .prettierrc는 이렇게 되어 있다.

{
  "tabWidth": 2,
  "trailingComma": "es5"
}

하나씩 보면

tabWidth

"tabWidth": 2

들여쓰기 기준을 2칸으로 둔다.

Next.js, React, TypeScript 프로젝트에서 흔히 쓰는 무난한 설정이다.

trailingComma

"trailingComma": "es5"

객체, 배열처럼 ES5에서 허용되는 위치에는 마지막 쉼표를 붙인다.

const routes = [
  "landing",
  "admin",
  "user",
];

이렇게 해두면 나중에 항목을 하나 추가할 때 diff가 조금 더 깔끔해진다.

 const routes = [
   "landing",
   "admin",
   "user",
+  "event",
 ];

마지막 쉼표가 없으면 기존 줄도 같이 바뀌는 경우가 있는데, 그걸 줄여준다.


설치된 패키지

package.json 기준으로 관련 패키지는 이렇게 들어가 있다.

패키지버전역할
prettier^3.8.3코드 포맷팅
husky^9.1.7Git Hook 관리
lint-staged^17.0.5staged 파일 대상 명령 실행
eslint-config-prettier^10.1.8ESLint와 Prettier의 포맷 규칙 충돌 방지

그리고 자동화에 직접 연결된 설정 파일은 아래와 같다.

파일적용 내용
.prettierrcPrettier 포맷 규칙
package.jsonprepare, lint-staged 설정
.husky/pre-commit커밋 전 pnpm lint-staged 실행
eslint.config.mjseslint-config-prettier 적용
.github/workflows/ci.ymlPR에서 lint, test, build 검증

ESLint와 Prettier 충돌 막기

Stamply 프로젝트의 eslint.config.mjs에는 eslint-config-prettier가 들어가 있다.

import prettier from "eslint-config-prettier";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  prettier,
  globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
]);

이 설정은 Prettier와 충돌할 수 있는 ESLint 포맷팅 규칙을 꺼준다.

eslint-config-prettier가 코드를 포맷팅해주는게 아니다!!!


실제 커밋 흐름

현재 프로젝트 기준으로 커밋할 때 흐름은 이렇다.

1. 파일 수정
2. git add로 커밋할 파일을 stage에 올림
3. git commit 실행
4. Husky가 .husky/pre-commit 실행
5. pnpm lint-staged 실행
6. staged 파일 중 js, jsx, ts, tsx, css만 찾음
7. 해당 파일에 prettier --write 실행
8. 문제가 없으면 커밋 생성

예를 들어 components/admin/common/Header.tsx를 수정하고 stage에 올렸다면,

git add components/admin/common/Header.tsx
git commit -m "feat: 헤더 수정"

커밋 직전에 아래 명령이 자동으로 실행되는 셈이다.

pnpm lint-staged

그리고 Header.tsx**/*.{js,jsx,ts,tsx,css} 조건에 걸리기 때문에 prettier --write 대상이 된다.


GitHub Hook? GitHub Actions?

여기서 용어가 조금 헷갈릴 수 있다. 내가 헷갈렸다.

보통 "hook"이라고 하면 크게 세 가지를 섞어서 부르는 느낌이다.

이름실행 위치설명
Git Hook내 로컬 Gitpre-commit, pre-push 같은 Git 이벤트 스크립트
GitHub WebhookGitHub → 외부 서버GitHub 이벤트가 발생하면 외부 URL로 payload를 보내는 기능
GitHub ActionsGitHub 서버PR, push 같은 이벤트에 맞춰 CI/CD workflow를 실행하는 기능

Stamply에서 Prettier 자동 적용에 직접 쓰는 건 Git Hook + Husky + lint-staged 조합이다.

그리고 GitHub 쪽에는 .github/workflows/ci.yml이 있다.

on:
  pull_request:
    branches:
      - develop
      - main

이 workflow는 develop, main 브랜치로 들어가는 PR에서 실행된다.

실행하는 일은 아래 순서다.

Checkout
  ↓
pnpm 11.1.1 설치
  ↓
Node.js 22 설정
  ↓
pnpm install --frozen-lockfile
  ↓
pnpm lint
  ↓
pnpm test
  ↓
pnpm build

여기서 중요한 점은 GitHub Actions는 현재 Prettier를 자동으로 고쳐주지 않는다.

로컬 커밋 전에 Husky가 자동 포맷팅을 하고, GitHub Actions는 PR에서 lint, test, build를 검증한다.

로컬 Husky
  → 커밋 전에 자동으로 고침

GitHub Actions
  → PR에서 문제가 없는지 검증

물론 둘 다 있으면 좋다~

로컬 hook에선 빠르게 고쳐주고, GitHub Actions는 누군가 hook을 건너뛰거나 환경 차이가 생겼을 때 마지막 검증선이 된다.


직접 확인해보자

의존성을 설치하고,

pnpm install

테스트용으로 staged 파일을 만든 뒤 커밋을 시도하면 된다.

git add app/page.tsx
git commit -m "test: prettier hook"

커밋 중간에 lint-staged가 실행되면 정상이다.

수동으로 staged 파일 대상 명령만 실행해보고 싶다면 이렇게도 할 수 있다.

pnpm lint-staged

전체 파일을 한 번에 포맷팅하고 싶다면 아래 명령을 쓴다.

pnpm exec prettier --write .

다만 이 명령은 전체 프로젝트를 대상으로 하므로 실행 전 변경 범위를 확인해 봐야 한다.



참고 자료

0개의 댓글