Next.js 엔지니어링 설정: ESLint + Prettier + Husky 한 번에 구축하기

금요일 밤의 악몽
지난 금요일 저녁, 컴퓨터를 끄고 퇴근하려던 순간 Slack에서 기술 책임자가 보낸 메시지를 봤습니다. “PR에 서식 문제가 너무 많은데, 정리한 뒤 다시 올려 주실 수 있나요?” GitHub를 열어 보니 화면 가득 빨간 diff가 보였습니다. 어떤 곳은 큰따옴표를 쓰고, 어떤 곳은 들여쓰기가 달랐기 때문이었습니다.
솔직히 정말 허탈했습니다. 제출 전에 분명 npm run lint를 실행했다고 기억했는데 왜 또 문제가 생긴 걸까요? 더 난감한 점은 팀의 다른 동료도 비슷한 일을 겪었다는 것입니다. 코드 로직에는 아무 문제가 없었지만 서식이 제 코드와 달라서 병합할 때 의미 없는 충돌이 잔뜩 발생했습니다.
이런 경험이 있나요? 코드는 잘 작성했는데 서식 문제 때문에 PR review를 몇 번씩 주고받으며 모두의 시간을 낭비하는 상황 말입니다. 그때 저는 이런 사소한 일을 도구가 자동으로 처리해 주는 확실한 방법이 없을까 생각했습니다.
답은 있습니다. 바로 ESLint + Prettier + Husky 조합입니다.
왜 이 세 가지 도구가 필요할까요?
솔직히 처음 이 세 이름을 들었을 때는 저도 조금 복잡하다고 느꼈습니다. 하지만 한동안 사용하고 나니 이전 방식으로는 돌아갈 수 없었습니다. 각각 어떤 가치가 있는지 살펴보겠습니다.
ESLint: 코드 품질의 수호자
많은 사람이 ESLint를 코드 서식 검사 도구로만 생각하지만, 사실 더 중요한 역할은 잠재적인 코드 문제를 찾아내는 것입니다.
예를 들어 Next.js는 HTML의 <img> 태그 대신 <Image> 컴포넌트를 사용하도록 권장합니다. <Image>에는 자동 최적화 기능이 있기 때문입니다. 실수로 <img>를 작성하면 ESLint가 즉시 다음과 같은 오류로 알려 줍니다.
Error: Do not use <img>. Use Image from 'next/image' instead.
이런 알림은 개발 단계에서 성능 문제를 막아 줍니다. 출시 후 이미지 로딩이 느리다는 사실을 발견하고 최적화하는 것보다 훨씬 효율적입니다.
Next.js 15에서는 ESLint가 기본으로 활성화되어 있지만, 그 가치를 충분히 활용하려면 올바르게 설정해야 합니다.
Prettier: 서식 지정의 최종 해법
ESLint가 코드 로직과 품질에 집중한다면 Prettier는 코드 서식에 집중합니다. 두 도구의 역할은 분명히 다르므로 혼동하지 않아야 합니다.
예전에는 팀에서 서식 문제로 자주 논쟁했습니다. 어떤 사람은 작은따옴표를, 다른 사람은 큰따옴표를 선호했고, 들여쓰기도 2칸과 4칸으로 나뉘었습니다. code review를 할 때마다 중요하지 않은 세부 사항을 논의하느라 시간을 많이 낭비했습니다.
Prettier를 설정한 뒤 이런 문제는 완전히 사라졌습니다. 프로젝트를 시작할 때 팀원과 규칙을 한 번 정하기만 하면 됩니다. 예를 들어 작은따옴표와 2칸 들여쓰기를 통일하면 이후에는 Prettier가 모든 코드를 자동으로 정리합니다. 한 번 설정하면 계속 혜택을 누릴 수 있습니다.
Husky: 자동화의 핵심
아무리 좋은 도구도 직접 실행해야 한다면 누군가는 잊기 마련입니다.
저도 로컬 개발 중 npm run lint 실행을 잊고 바로 코드를 제출했다가 CI가 실패해 팀 전체의 배포를 막은 적이 여러 번 있습니다. 그 난감함을 다시 겪고 싶지는 않습니다.
Husky는 코드를 커밋하기 전에 검사를 자동으로 실행합니다. Git의 pre-commit 단계에서 커밋을 가로채 ESLint와 Prettier를 먼저 실행하고, 코드가 규칙을 통과해야만 커밋을 허용합니다.
lint-staged와 함께 사용하면 Husky는 프로젝트 전체가 아니라 수정한 파일만 검사하므로 매우 빠릅니다. pre-commit hook이 개발 흐름을 늦출까 걱정할 필요가 없습니다.
세 도구를 함께 사용할 때의 효과
세 도구를 조합한 작업 흐름은 다음과 같습니다.
- ESLint가 코드 품질 규칙을 정의합니다. 예를 들어
var를 금지하고const나let만 사용하도록 합니다. - Prettier가 코드 서식을 통일합니다. 예를 들어 모든 문자열에 작은따옴표를 사용합니다.
- Husky가 커밋 전에 검사를 자동으로 실행해 누구도 빠뜨리지 않게 합니다.
팀이 얻는 이점은 분명합니다.
- PR review에서 서식 문제가 아니라 비즈니스 로직에 집중할 수 있습니다.
- 서식이 통일되어 코드 병합 충돌이 줄어듭니다.
- 커밋 전에 이미 검사하므로 CI 실패율이 낮아집니다.
전체 설정 과정
이제 실습을 시작하겠습니다. 시행착오를 최대한 피할 수 있도록 이 도구 체인을 단계별로 설정해 보겠습니다.
1단계: Next.js 프로젝트 초기화
이미 프로젝트가 있다면 이 단계는 건너뛰어도 됩니다. 새 프로젝트라면 다음 명령을 실행합니다.
npx create-next-app@latest my-app
cd my-app
프로젝트를 만들 때 TypeScript와 ESLint를 선택하세요. Next.js 15에서는 ESLint를 기본으로 활성화해 주므로 수고를 덜 수 있습니다.
2단계: 의존성 설치
다음 명령을 실행합니다. 여기서는 pnpm을 사용하지만 npm이나 yarn을 사용해도 됩니다.
pnpm add -D eslint eslint-config-next prettier eslint-config-prettier husky lint-staged
각 패키지의 역할은 다음과 같습니다.
eslint: ESLint 코어eslint-config-next: Next.js 고유 규칙을 포함한 공식 ESLint 설정prettier: Prettier 코어eslint-config-prettier: Prettier와 충돌하는 ESLint 서식 규칙 비활성화husky: Git hooks 관리 도구lint-staged: 스테이징한 파일에만 검사 실행
주의: eslint-plugin-prettier는 설치하지 마세요. 많은 튜토리얼에서 이 패키지를 권장하지만, Prettier를 ESLint 규칙으로 실행해 성능 문제를 일으킬 수 있습니다. ESLint와 Prettier는 따로 실행하는 것이 올바른 방식입니다.
3단계: ESLint 설정
특히 Next.js 15가 ESLint 9로 업그레이드되면서 설정 형식이 바뀌었기 때문에 이 단계에서 가장 문제가 생기기 쉽습니다.
ESLint 9의 Flat Config 형식 사용(권장)
프로젝트 루트에 eslint.config.mjs를 만듭니다.
// eslint.config.mjs
import { FlatCompat } from '@eslint/eslintrc';
import nextPlugin from '@next/eslint-plugin-next';
const compat = new FlatCompat();
export default [
...compat.extends('next/core-web-vitals'),
{
plugins: {
'@next/next': nextPlugin,
},
rules: {
'@next/next/no-img-element': 'error',
'react/no-unescaped-entities': 'off',
// 여기에 필요한 규칙을 추가할 수 있습니다
},
},
{
ignores: ['.next/', 'node_modules/', 'out/'],
},
];
핵심 사항:
- ESLint 9는 더 이상
.eslintrc.json을 사용하지 않고 flat config 형식인eslint.config.mjs를 사용합니다. - Next.js 15로 업그레이드한 뒤 “The Next.js plugin was not detected” 오류가 발생한다면 설정 형식이 잘못되었을 가능성이 큽니다.
- Next.js 16에서는
next lint명령이 제거될 예정이므로 새 형식에 미리 익숙해지는 편이 좋습니다.
하위 버전 방식(호환성 문제가 있을 때)
당장 flat config를 다루고 싶지 않다면 환경 변수를 설정해 ESLint 8의 설정 형식으로 되돌릴 수 있습니다.
ESLINT_USE_FLAT_CONFIG=false
그런 다음 .eslintrc.json을 계속 사용합니다.
{
"extends": ["next/core-web-vitals", "prettier"],
"rules": {
"@next/next/no-img-element": "error"
}
}
그래도 앞으로의 표준이므로 가능한 한 빨리 flat config로 마이그레이션하는 것을 권합니다.
4단계: Prettier 설정
프로젝트 루트에 .prettierrc.json을 만듭니다.
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 80,
"arrowParens": "avoid"
}
제가 선호하는 설정이지만 팀의 습관에 맞게 조정할 수 있습니다.
singleQuote: true: 더 간결해 보여서 작은따옴표를 선호합니다.printWidth: 80: 한 줄을 최대 80자로 제한하므로 여러 편집기 창을 나란히 띄우기 좋습니다.trailingComma: 'es5': 객체와 배열의 마지막 항목에 쉼표를 추가해 Git diff를 더 깔끔하게 만듭니다.
이어서 .prettierignore를 만들어 Prettier가 무시할 파일을 지정합니다.
.next
out
node_modules
public
*.lock
VSCode 연동(선택 사항이지만 권장)
VSCode를 사용한다면 .vscode/settings.json을 만들어 저장할 때 자동으로 서식을 지정할 수 있습니다.
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
이제 Ctrl + S를 눌러 파일을 저장할 때마다 Prettier가 코드를 자동으로 정리합니다. 무척 편리합니다.
5단계: Husky + lint-staged 설정
이 단계는 전체 설정 과정의 핵심이며 팀 협업 효율을 가장 크게 높여 줍니다.
Husky 초기화
다음을 실행합니다.
pnpm dlx husky init
이 명령은 .husky/ 폴더를 자동으로 만들고 package.json에 prepare 스크립트를 추가합니다.
{
"scripts": {
"prepare": "husky install"
}
}
prepare 스크립트는 팀원이 pnpm install을 실행할 때 Husky hooks도 자동으로 설치되게 합니다. 새로 합류한 동료도 추가 작업 없이 프로젝트를 복제한 뒤 바로 자동 검사의 보호를 받을 수 있습니다.
pre-commit hook 설정
.husky/pre-commit 파일을 다음과 같이 수정합니다.
pnpm lint-staged
이것으로 끝입니다. 이제 git commit을 실행할 때마다 Husky가 먼저 pnpm lint-staged를 실행합니다.
lint-staged 설정
프로젝트 루트에 .lintstagedrc.mjs를 만듭니다.
export default {
'*.{js,jsx,ts,tsx}': [
'eslint --fix',
'prettier --write',
],
'*.{json,md,css}': [
'prettier --write',
],
};
이 설정은 다음을 의미합니다.
.js,.jsx,.ts,.tsx파일에는 먼저eslint --fix로 문제를 자동 수정하고, 이어서prettier --write로 서식을 지정합니다..json,.md,.css파일에는prettier --write만 실행합니다.
성능 최적화 팁:
- 스테이징한 파일만 검사: lint-staged는
git add로 추가한 파일을 자동으로 골라내므로 프로젝트 전체를 검사하지 않습니다. - 전체 검사 피하기: pre-commit에서
pnpm lint를 실행하면 프로젝트 전체를 검사해 매우 느려지므로 사용하지 마세요. - 테스트 건너뛰기: pre-commit에서는 서식 검사와 기본적인 코드 품질 검사만 하고, 복잡한 테스트는 CI에 맡깁니다.
- 긴급 커밋 시 건너뛰기: 운영 문제를 긴급하게 수정하는 등 검사를 꼭 건너뛰어야 한다면
git commit --no-verify를 사용할 수 있습니다.
자주 발생하는 문제 해결
Windows 환경에서 Husky가 작동하지 않는 경우
Windows를 사용한다면 Git 설정에서 CMD나 PowerShell이 아니라 Git Bash를 사용하는지 확인하세요. 또한 .husky/pre-commit 파일에 실행 권한이 있는지 확인합니다.
chmod +x .husky/pre-commit
hook 실행이 너무 느린 경우
pre-commit hook 실행에 10초 이상 걸린다면 lint-staged 설정을 확인해 실수로 프로젝트 전체를 검사하고 있지 않은지 살펴보세요. 정상이라면 파일 몇 개를 검사하는 lint-staged는 2~3초 안에 끝나야 합니다.
설정 검증
설정이 끝났으니 제대로 작동하는지 확인해 보겠습니다.
테스트 절차
- 아무 파일이나 수정해 일부러 서식 문제를 만듭니다. 예를 들어 작은따옴표를 큰따옴표로 바꾸거나 세미콜론 몇 개를 지웁니다.
git add .을 실행합니다.git commit -m "test"를 실행합니다.- 터미널 출력을 확인합니다.
성공 기준
올바르게 설정했다면 다음과 비슷한 출력이 표시됩니다.
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
이후 수정한 파일을 열면 서식 문제가 자동으로 고쳐진 것을 볼 수 있습니다. 이것이 자동화의 매력입니다.
실패한 경우
.husky/pre-commit파일이 존재하고 내용이 올바른지 확인합니다..lintstagedrc.mjs파일이 프로젝트 루트에 있는지 확인합니다.pnpm lint-staged를 직접 실행해 오류 메시지가 나오는지 확인합니다.
고급 설정
기본 설정만으로도 대부분의 요구 사항을 충족할 수 있지만, 코드 품질을 더 엄격하게 관리하려면 다음 고급 설정을 고려해 보세요.
commitlint 추가(커밋 메시지 규칙 적용)
코드 서식뿐 아니라 commit message의 규칙도 중요합니다. commitlint를 사용하면 모든 팀원이 Conventional Commits 같은 통일된 커밋 메시지 형식을 따르게 할 수 있습니다.
의존성을 설치합니다.
pnpm add -D @commitlint/cli @commitlint/config-conventional
commitlint.config.mjs를 만듭니다.
export default {
extends: ['@commitlint/config-conventional'],
};
commit-msg hook을 추가합니다.
echo "pnpm commitlint --edit \$1" > .husky/commit-msg
이제 커밋 메시지가 규칙에 맞지 않으면 커밋이 차단됩니다. 예를 들어 git commit -m "fix: bug" 대신 git commit -m "fix bug"를 사용하면 차단됩니다.
TypeScript 타입 검사 추가
커밋 전에 TypeScript 타입 검사를 자동으로 실행하고 싶다면 .lintstagedrc.mjs를 다음과 같이 수정합니다.
export default {
'*.{ts,tsx}': [
() => 'tsc --noEmit', // 타입 검사
'eslint --fix',
'prettier --write',
],
'*.{js,jsx}': [
'eslint --fix',
'prettier --write',
],
'*.{json,md,css}': [
'prettier --write',
],
};
주의: tsc --noEmit는 프로젝트 전체의 타입을 검사하므로 느릴 수 있습니다. 프로젝트 규모가 크다면 pre-commit hook을 지연시킬 수 있습니다. 개인적으로는 타입 검사를 CI에 맡기고 pre-commit에서는 서식과 기본 검사만 하는 것을 권합니다.
Monorepo 설정
pnpm workspace나 Turborepo를 사용하는 Monorepo라면 설정이 조금 더 복잡합니다.
- 루트 디렉터리에 Husky와 lint-staged를 설치합니다.
- 각 package 아래에 별도의
.lintstagedrc.mjs를 만듭니다. - 설정이 다른 package로 ‘누출’되지 않게 합니다.
자세한 설정은 lint-staged 공식 문서를 참고하세요.
자주 발생하는 문제와 해결 방법
설정 과정에서 몇 가지 문제를 만날 수 있습니다. 흔히 발생하는 문제와 해결책을 정리했습니다.
Q1: ESLint와 Prettier 규칙이 충돌하면 어떻게 하나요?
증상: ESLint는 특정 줄의 서식이 잘못됐다고 오류를 내지만, Prettier로 정리하면 다시 원래 모습으로 돌아갑니다.
해결 방법:
eslint-config-prettier가 설치되어 있는지 확인하고 ESLint 설정에서 마지막에 prettier를 extends하도록 합니다.
export default [
...compat.extends('next/core-web-vitals'),
...compat.extends('prettier'), // 마지막에 배치
];
eslint-config-prettier는 Prettier와 충돌하는 ESLint 서식 규칙을 모두 끕니다. 따라서 Prettier는 서식에, ESLint는 코드 품질에 집중할 수 있습니다.
Q2: Next.js 15 업그레이드 후 ESLint에서 “plugin not detected” 오류가 발생합니다
증상: pnpm lint를 실행하면 다음 오류가 발생합니다.
Error: The Next.js plugin was not detected in your ESLint configuration.
해결 방법:
대개 ESLint 9의 flat config 형식이 올바르지 않아서 발생합니다. eslint.config.mjs에서 @next/eslint-plugin-next를 제대로 가져왔는지 확인합니다.
import nextPlugin from '@next/eslint-plugin-next';
export default [
{
plugins: {
'@next/next': nextPlugin,
},
},
];
계속 해결되지 않는다면 임시로 ESLINT_USE_FLAT_CONFIG=false를 설정해 이전 형식으로 되돌릴 수 있습니다.
Q3: pre-commit hook이 너무 느릴 때는 어떻게 최적화하나요?
증상: 커밋할 때마다 10초 이상 기다려야 해 개발 효율이 크게 떨어집니다.
해결 방법:
- lint-staged가 스테이징한 파일만 검사하는지 확인합니다. 정상적인 경우 자동으로 처리됩니다.
- pre-commit에서 테스트 스크립트를 제거합니다. 테스트는 CI 단계에서 실행해야 합니다.
- 프로젝트 규모가 크다면
.ts와.tsx파일에만 ESLint를 실행하고 나머지 파일에는 Prettier만 실행하는 방법을 고려합니다.
Q4: 팀원에게 Husky hooks가 설치되지 않았습니다
증상: 새 동료가 프로젝트를 복제한 뒤 코드를 커밋해도 pre-commit hook이 실행되지 않습니다.
해결 방법:
package.json에 prepare 스크립트가 있는지 확인합니다.
{
"scripts": {
"prepare": "husky install"
}
}
동료에게 pnpm install을 한 번 실행하도록 안내하면 Husky hooks가 자동으로 설치됩니다.
Q5: Windows 환경에서 Husky가 작동하지 않습니다
증상: Windows 사용자가 코드를 커밋할 때 pre-commit hook이 실행되지 않습니다.
해결 방법:
- Git 설정에서 CMD가 아니라 Git Bash를 사용하는지 확인합니다.
.husky/pre-commit파일 권한을 확인하고 실행 권한을 직접 추가해 봅니다.
chmod +x .husky/pre-commit
- 그래도 작동하지 않으면 Husky를 다시 초기화합니다.
rm -rf .husky
pnpm dlx husky init
정리와 모범 사례
이 도구 체인을 설정하는 데 약 30분이 걸렸지만, 팀이 얻는 가치는 오래 지속됩니다.
핵심 이점
- 코드 품질 향상: ESLint가 개발 단계에서 잠재적 문제를 찾아 운영 장애를 예방합니다.
- 효율적인 팀 협업: 통일된 코드 서식으로 불필요한 PR 논쟁과 병합 충돌이 줄어듭니다.
- 자동화된 안전장치: Husky가 모든 커밋의 규칙 준수를 보장하므로 검사를 잊을 걱정이 없습니다.
모범 사례 권장 사항
- 점진적으로 설정하기: 처음부터 지나치게 엄격한 규칙을 설정하지 마세요. 기본 설정으로 한동안 사용한 뒤 문제가 발견될 때 단계적으로 조정합니다.
- 팀 합의 만들기: 작은따옴표와 큰따옴표 중 무엇을 쓸지 같은 Prettier 설정은 팀원이 함께 논의해 결정해야 합니다. 혼자 정하지 마세요.
- 성능 우선하기: pre-commit hook에서는 꼭 필요한 검사만 하고 복잡한 테스트는 CI에 맡깁니다.
- 정기적으로 업데이트하기: Next.js와 ESLint의 버전 변화를 살펴보세요. 특히 Next.js 16에서는
next lint명령이 제거될 예정이라는 점에 유의해야 합니다.
다음 단계
아직 프로젝트에 이 도구 체인을 설정하지 않았다면 지금 시도해 보세요. 이 글을 따라 단계별로 작업하면 30분이면 충분합니다.
그런 다음 설정을 팀원과 공유해 모두의 개발 환경을 통일합니다.
마지막으로 팀의 구체적인 상황에 맞게 Prettier와 ESLint 규칙을 조정하세요. 도구는 사람을 위해 존재하므로 도구에 끌려다니지 않는 것이 중요합니다.
개인적인 경험
솔직히 이 도구 체인을 처음 설정할 때는 꽤 번거롭게 느껴졌습니다. 특히 ESLint 9의 flat config 마이그레이션 문제를 만났을 때는 저도 한참 헤맸습니다. 하지만 사용한 뒤에는 이전으로 돌아갈 수 없었습니다.
이제는 코드를 커밋할 때 서식 문제나 lint 실행을 잊는 일을 전혀 걱정하지 않습니다. pre-commit hook이 모든 것을 자동으로 검사해 주는 안정감이 정말 좋습니다.
팀의 PR review 효율도 눈에 띄게 높아졌습니다. 예전에는 PR에서 “여기는 작은따옴표를 써야 할까요, 큰따옴표를 써야 할까요?”라고 토론했지만, 이제는 Prettier가 이미 통일해 주므로 그런 일이 없습니다. 서식의 세부 사항 대신 비즈니스 로직과 아키텍처 설계를 논의하는 데 시간을 쓸 수 있습니다.
자동화가 가져오는 이런 효율 향상은 설정에 들이는 시간을 충분히 보상합니다.
이 글이 도움이 되었기를 바랍니다. 설정 과정에서 문제가 생기면 댓글로 알려 주세요. 가능한 한 도와드리겠습니다.
설정이 순조롭게 끝나길 바랍니다!
3분 읽기 · 게시일: 2026년 1월 6일 · 수정일: 2026년 9월 8일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js TypeScript 고급 설정: tsconfig 최적화와 타입 안전성 실전 가이드
Next.js TypeScript 설정 최적화 방법을 깊이 있게 다룹니다. tsconfig 엄격 모드 설정, 타입 안전 라우팅 구현, 환경 변수 타입 정의를 통해 any 타입을 없애고 개발 경험을 개선하는 방법을 알아봅니다.
45편 중 26편
다음
Next.js 로딩 상태 관리: loading.tsx와 Suspense 실전 가이드
Next.js의 loading.tsx와 Suspense 활용법을 익혀 직접 작성한 useState 로딩 코드를 줄이고, 최소한의 코드로 전문적인 로딩 경험을 구현하는 방법을 알아봅니다. 스켈레톤 UI 구현, 동적 라우트 처리, 자주 발생하는 문제의 해결책까지 다룹니다.
45편 중 28편



댓글
GitHub로 로그인하여 댓글을 남기세요