Prettier 설정과 도입: 옵션, ESLint와 역할 나누기, VS Code·pre-commit 연동

이 글의 핵심

Prettier의 주요 옵션과 설정 파일, ESLint와 충돌하지 않게 역할을 나누는 방법, 에디터 저장 시 포맷과 커밋 전 자동 포맷 설정, 파일별 예외 처리를 다룹니다.

이 글의 핵심

Prettier로 코드 포맷팅을 자동화하는 글입니다. 설정, ESLint 통합, Pre-commit Hook, IDE 통합을 예제로 정리했습니다.

실무에서 마주치는 문제들

팀원마다 포맷이 달라요

에디터 설정이 사람마다 달라서 한 사람은 탭, 다른 사람은 공백 2칸으로 저장하면, 기능 변경은 한 줄인데 diff에는 파일 전체가 바뀐 것으로 나옵니다. 이런 커밋이 섞이면 git blame으로 코드의 실제 작성자를 찾기도 어려워집니다.

코드 리뷰가 스타일 논쟁이 돼요

“여기 줄바꿈이 어색하다”, “세미콜론을 빼자” 같은 코멘트는 정답이 없어서 리뷰 시간을 잡아먹고, 정작 로직 문제는 덜 보게 만듭니다. Prettier는 선택지를 거의 주지 않는 대신 결과를 기계적으로 결정해서 이런 논쟁 자체를 없앱니다.

수동 포맷팅이 번거로워요

긴 함수 호출의 인자를 줄바꿈하고 들여쓰기를 맞추는 일을 손으로 하면 시간이 들고 실수도 생깁니다. Prettier는 코드를 AST(구문 트리)로 파싱한 뒤 원래 서식을 거의 무시하고 처음부터 다시 출력하므로, 누가 어떻게 썼든 같은 코드는 같은 모양이 됩니다.

이 “원래 서식을 무시한다”는 성질이 곧 한계이기도 합니다. 정렬을 맞춰 둔 표 형태의 배열이나 의도적으로 한 줄에 모아 둔 코드도 Prettier가 다시 배치합니다. 이런 곳은 // prettier-ignore 주석으로 다음 노드 하나를 포맷에서 제외할 수 있습니다.


Prettier가 하는 일

핵심 특징

Prettier는 Opinionated 코드 포맷터입니다. 주요 장점:

  • 자동 포맷팅: 일관된 스타일
  • Opinionated: 설정 최소화
  • 다양한 언어: JS, TS, CSS, HTML, JSON
  • IDE 통합: 저장 시 자동
  • ESLint 통합: 겹치는 규칙을 꺼서 함께 사용

설치와 기본 사용

설치

npm install -D prettier

Prettier는 정확한 버전으로 고정하는 것이 공식 권장입니다(npm install -D --save-exact prettier). 마이너 버전 업데이트에서도 출력 결과가 조금씩 바뀌기 때문에, ^3.2.0처럼 범위로 두면 팀원마다 설치된 버전이 달라져 같은 파일을 서로 다르게 포맷하고, CI의 --check만 실패하는 상황이 생깁니다. 버전을 올릴 때는 한 커밋에서 prettier --write .로 전체를 다시 포맷하고, 그 커밋 해시를 .git-blame-ignore-revs에 넣어 두면 git blame이 포맷 변경 커밋을 건너뜁니다.

.prettierrc

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100,
  "arrowParens": "always",
  "endOfLine": "lf"
}

package.json

{
  "scripts": {
    "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
    "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,md}\""
  }
}

--write는 파일을 직접 고치고, --check는 포맷이 맞지 않는 파일이 있으면 목록을 출력하고 종료 코드 1로 끝납니다. 로컬에서는 format, CI에서는 format:check를 쓰는 식으로 나눕니다. glob을 따옴표로 감싸는 이유는 셸이 먼저 **를 해석하지 않고 Prettier에 그대로 넘기게 하기 위해서입니다. 따옴표가 없으면 셸 설정에 따라 하위 폴더 파일이 빠지는데, 이것이 “로컬에서는 통과하는데 CI에서 실패한다”의 원인이 되기도 합니다. 사실 Prettier 3는 prettier --write .처럼 디렉토리를 넘기면 지원하는 모든 확장자를 알아서 찾으므로, 확장자 목록을 관리하기 싫다면 이 방식이 더 단순합니다.


팀에서 합의할 설정 옵션

설정 파일 예시입니다. 옵션 대부분은 기본값이 이미 합리적이라, 팀에서 정말 의견이 갈리는 몇 가지만 적는 편이 좋습니다.

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "useTabs": false,
  "trailingComma": "es5",
  "printWidth": 100,
  "arrowParens": "always",
  "bracketSpacing": true,
  "jsxSingleQuote": false,
  "endOfLine": "lf"
}

옵션 몇 가지를 짚어 보면, printWidth는 한 줄 최대 길이가 아니라 Prettier가 줄을 나눌지 판단하는 목표 길이입니다. 긴 문자열이나 URL처럼 나눌 수 없는 부분은 이 값을 넘어도 그대로 둡니다. 기본값 80을 100이나 120으로 올리는 팀이 많은데, 값을 키우면 한 줄에 들어가는 인자가 늘어 오히려 diff가 읽기 어려워질 수 있습니다.

trailingComma는 여러 줄에 걸친 배열·객체·인자 목록의 마지막 항목 뒤에 쉼표를 붙일지 정합니다. 쉼표가 있으면 항목을 추가할 때 diff가 한 줄만 바뀝니다. Prettier 3에서 기본값이 "es5"에서 "all"로 바뀌었기 때문에, Prettier 2 시절 설정을 그대로 가져왔다면 이 값이 의도대로인지 확인할 필요가 있습니다. "all"은 함수 매개변수에도 쉼표를 붙이는데, 아주 오래된 런타임에서는 문법 에러가 날 수 있습니다.

endOfLine: "lf"는 줄 끝 문자를 LF로 통일합니다. Windows에서 Git의 core.autocrlf가 켜져 있으면 체크아웃할 때 CRLF로 바뀌어, Prettier가 모든 줄에 Delete ␍ 경고를 내는 상황이 생깁니다. 저장소에 .gitattributes 파일로 * text=auto eol=lf를 지정해 Git 단계에서부터 LF로 맞춰 두는 것이 가장 확실한 해결책입니다.


ESLint와 충돌 없이 함께 쓰기

ESLint에도 들여쓰기, 따옴표 같은 스타일 규칙이 있어서 둘을 그냥 함께 쓰면 Prettier가 고친 코드를 ESLint가 에러로 잡고, ESLint가 고친 코드를 Prettier가 다시 바꾸는 충돌이 생깁니다. 해결 방법은 스타일은 Prettier에, 코드 품질은 ESLint에 맡기고 ESLint의 스타일 규칙을 끄는 것입니다.

설치

npm install -D eslint-config-prettier eslint-plugin-prettier

.eslintrc.json

{
  "extends": [
    "eslint:recommended",
    "prettier"
  ],
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error"
  }
}

두 패키지는 역할이 다릅니다. eslint-config-prettier는 Prettier와 겹치는 ESLint 스타일 규칙을 끄기만 하는 설정이라, extends 배열의 마지막에 두어야 앞의 설정이 켠 규칙까지 덮어씁니다. 순서를 앞에 두면 뒤에 오는 설정이 규칙을 다시 켜서 충돌이 남습니다. eslint-plugin-prettier는 Prettier를 ESLint 규칙(prettier/prettier) 하나로 실행해, 포맷이 맞지 않는 곳을 ESLint 에러로 보고합니다.

Prettier 공식 문서는 두 번째 방식(eslint-plugin-prettier)을 대부분의 경우 권하지 않습니다. 포맷 차이가 에디터에 빨간 밑줄로 가득 표시되어 실제 버그 경고가 묻히고, ESLint가 파일마다 Prettier를 한 번 더 실행하므로 린트가 느려지기 때문입니다. eslint-config-prettier만 쓰고, 포맷은 에디터 저장 시와 커밋 전에 Prettier를 따로 실행하는 구성이 더 단순합니다. 둘 중 어느 쪽이든 eslint --fix 한 번으로 모두 처리하고 싶은 팀이라면 플러그인 방식도 선택지가 됩니다.

ESLint 9부터는 .eslintrc.json 대신 eslint.config.js(flat config)가 기본입니다. flat config에서는 import eslintConfigPrettier from 'eslint-config-prettier'로 가져와 설정 배열의 마지막 요소로 넣습니다. 이 글의 .eslintrc 예제는 ESLint 8 이하 기준이며, ESLint 9에서 그대로 쓰면 ESLint couldn't find an eslint.config.(js|mjs|cjs) file 에러가 납니다.


.prettierignore

.prettierignore

node_modules/
dist/
build/
coverage/
*.min.js
package-lock.json

.prettierignore는 .gitignore와 같은 문법입니다. Prettier 3는 기본적으로 .gitignore에 적힌 경로도 무시하므로 node_modules나 dist를 두 번 적을 필요는 없지만, 명시해 두면 의도가 분명해집니다. 자동 생성 코드(GraphQL 코드젠 결과, Prisma 클라이언트, OpenAPI 타입)는 다음 생성 때 어차피 덮어써지므로 여기에 넣어 두어야 불필요한 diff가 생기지 않습니다. 반대로 CHANGELOG.md처럼 도구가 만든 마크다운을 포맷 대상에 두면 매번 다시 정렬되어 릴리스 도구와 충돌하기도 합니다.


VS Code에서 저장 시 포맷

설치

Prettier - Code formatter (esbenp.prettier-vscode)

.vscode/settings.json

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

이 파일을 .vscode/settings.json으로 저장소에 커밋하면 팀원 모두가 같은 설정을 쓰게 됩니다. 언어별 블록을 따로 둔 이유는, 사용자 설정에 [typescript] 전용 포맷터가 따로 지정되어 있으면 전역 editor.defaultFormatter보다 그것이 우선하기 때문입니다. “저장해도 포맷이 안 된다”거나 “다른 포맷터가 적용된다”면 이 우선순위부터 확인합니다. .vscode/extensions.json의 recommendations에 esbenp.prettier-vscode를 넣어 두면 저장소를 연 사람에게 확장 설치를 권유합니다.

VS Code 확장은 프로젝트의 node_modules에 설치된 Prettier를 우선 사용하고, 없으면 확장에 내장된 버전을 씁니다. 그래서 npm install을 하지 않은 상태에서는 확장에 번들된 버전으로 포맷되어 팀의 고정 버전과 결과가 달라질 수 있습니다. 설정 파일 변경이 반영되지 않을 때는 출력 패널의 “Prettier” 채널을 보면 어떤 설정 파일과 버전을 읽었는지 확인할 수 있습니다.


Husky로 커밋 전에 포맷하기

설치

npm install -D husky lint-staged
npx husky init

.husky/pre-commit

npx lint-staged

package.json

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,css,md}": [
      "prettier --write"
    ]
  }
}

에디터 설정만으로는 확장을 설치하지 않은 사람이나 다른 에디터 사용자의 커밋을 막을 수 없으므로, 커밋 직전에 한 번 더 포맷하는 단계를 둡니다. husky는 Git 훅을 저장소에 설정 파일로 두게 해 주고, lint-staged는 스테이징된 파일만 골라 명령을 실행합니다. 전체 프로젝트를 포맷하면 커밋마다 수십 초가 걸리지만, 바뀐 파일만 처리하면 거의 즉시 끝납니다. lint-staged는 명령이 파일을 고치면 그 변경을 자동으로 다시 스테이징합니다.

eslint --fix를 prettier --write보다 먼저 두는 순서에도 이유가 있습니다. ESLint의 자동 수정이 코드 모양을 바꿀 수 있으므로, 마지막에 Prettier가 최종 모양을 확정하게 하는 것입니다. npx husky init은 husky v9 기준 명령이고, v8 이전의 husky install이나 husky add 방식 글을 따라 하면 명령이 없다는 에러가 납니다. 훅은 git commit --no-verify로 건너뛸 수 있으므로, 최종 보증은 CI의 prettier --check가 맡아야 합니다.


파일별 overrides 설정

// prettier.config.js
module.exports = {
  semi: true,
  singleQuote: true,
  overrides: [
    {
      files: '*.json',
      options: {
        tabWidth: 4,
      },
    },
    {
      files: '*.md',
      options: {
        proseWrap: 'always',
      },
    },
  ],
};

overrides는 glob에 맞는 파일에만 옵션을 덮어씁니다. Markdown의 proseWrap: 'always'는 문단을 printWidth에 맞춰 강제로 줄바꿈하는데, 한 문장이 여러 줄로 나뉘면 문장 하나를 고쳐도 문단 전체가 다시 흘러 diff가 커집니다. 문서 리뷰를 diff로 하는 팀이라면 기본값 preserve가 나을 수 있습니다.

.js 설정 파일을 쓰면 조건에 따라 값을 계산할 수 있지만, package.json에 "type": "module"이 있는 프로젝트에서 module.exports를 쓰면 module is not defined in ES module scope 에러가 납니다. 이 경우 export default { ... }로 쓰거나 파일 이름을 prettier.config.cjs로 바꿉니다. 그리고 한 프로젝트에 .prettierrc와 prettier.config.js가 함께 있으면 하나만 적용되므로, 설정 파일은 하나로 정리해 두는 것이 헷갈리지 않습니다.


CLI에서 —write와 —check

--write는 파일을 그 자리에서 고치고, --check는 포맷이 맞지 않는 파일을 알려 주기만 한 뒤 0이 아닌 종료 코드로 끝나므로 CI에서는 --check를 씁니다.

# 포맷팅
prettier --write src/
# 체크만
prettier --check src/
# 특정 파일
prettier --write src/index.ts
# 여러 확장자
prettier --write "src/**/*.{js,ts,json}"

prettier를 전역 설치하지 않았다면 npx prettier ...로 실행해야 프로젝트에 고정된 버전이 쓰입니다. 전역 설치 버전과 프로젝트 버전이 다르면 같은 명령이라도 결과가 달라질 수 있습니다. 대량 포맷 전에는 prettier --list-different로 바뀔 파일 목록만 먼저 확인해 보는 것이 안전하고, 기존 프로젝트에 처음 도입할 때는 다른 작업과 섞지 말고 “전체 포맷” 전용 커밋을 따로 만드는 것이 리뷰와 이력 관리에 좋습니다.


ESLint·Husky까지 포함한 전체 설정

.prettierrc.json

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100,
  "arrowParens": "always",
  "endOfLine": "lf",
  "bracketSpacing": true,
  "jsxSingleQuote": false
}

.eslintrc.json

{
  "env": {
    "browser": true,
    "es2021": true,
    "node": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "plugins": [
    "@typescript-eslint",
    "react",
    "react-hooks",
    "prettier"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "prettier"
  ],
  "rules": {
    "prettier/prettier": "error",
    "@typescript-eslint/no-unused-vars": "error",
    "react/react-in-jsx-scope": "off"
  }
}

package.json

{
  "scripts": {
    "lint": "eslint src",
    "lint:fix": "eslint src --fix",
    "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
    "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,md}\""
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,css,md}": [
      "prettier --write"
    ]
  }
}

이 설정은 앞에서 다룬 조각을 모은 것이라, ESLint 쪽의 "prettier" 플러그인과 "prettier/prettier": "error"까지 켜진 구성입니다. 앞에서 설명한 이유로 플러그인 방식을 쓰지 않기로 했다면 plugins의 "prettier"와 해당 규칙 줄을 빼고 extends의 마지막 "prettier"만 남기면 됩니다. parserOptions.project를 지정하면 타입 정보를 쓰는 규칙을 켤 수 있지만 린트가 크게 느려지고, tsconfig.json의 include에 없는 파일(설정 파일 등)을 린트하면 Parsing error: ... was not found by the project service 류의 에러가 나므로, 타입 기반 규칙을 쓰지 않는다면 빼는 편이 낫습니다.


Prettier 요약

  • Prettier: 코드 포맷터
  • 자동 포맷팅: 일관된 스타일
  • Opinionated: 설정 최소화
  • 다양한 언어: JS, TS, CSS, HTML
  • ESLint 통합: eslint-config-prettier를 마지막에 두어 스타일 규칙 끄기
  • IDE 통합: 저장 시 자동
  • 버전 고정: --save-exact로 팀 전체 출력 결과 일치

구현 체크리스트

  • Prettier 설치
  • 설정 파일 생성
  • ESLint 통합
  • Ignore 파일 설정
  • VS Code 통합
  • Pre-commit Hook
  • 팀 공유
  • CI/CD 통합

같이 보면 좋은 글


자주 묻는 질문 (FAQ)

Q. ESLint와 충돌하지 않나요?

A. eslint-config-prettier를 extends(또는 flat config 배열)의 마지막에 두면 겹치는 스타일 규칙이 꺼져 충돌이 사라집니다. 순서가 앞에 있으면 뒤의 설정이 규칙을 다시 켜므로 위치가 중요합니다.

Q. 설정을 커스터마이징할 수 있나요?

A. 몇 가지 옵션은 바꿀 수 있지만, Prettier는 옵션을 늘리지 않는 것을 원칙으로 삼고 있어 “이 경우에만 줄바꿈하지 말라” 같은 세밀한 제어는 제공하지 않습니다. 그런 제어가 꼭 필요한 부분은 // prettier-ignore로 제외합니다.

Q. 모든 파일 타입을 지원하나요?

A. JavaScript, TypeScript, JSX, CSS·SCSS·Less, HTML, Vue, Angular 템플릿, JSON, YAML, Markdown, GraphQL을 기본 지원합니다. Svelte, PHP, Java 등은 커뮤니티 플러그인을 설치해야 합니다.

Q. Biome 같은 대안은 어떤가요?

A. Biome은 Rust로 작성된 도구로 포맷과 린트를 함께 처리하며 Prettier와 높은 호환성을 목표로 합니다. 속도가 크게 빠르다는 장점이 있지만, 지원 언어와 ESLint 플러그인 생태계는 아직 Prettier·ESLint 조합보다 좁으므로 필요한 규칙이 지원되는지 먼저 확인하는 것이 좋습니다.