Git Husky로 커밋 전에 린트 강제하기

정주·2026년 1월 5일

프로젝트

목록 보기
3/3

들어가며

린트는 사실 그동안 GPT나 팀원들이 세팅해준 설정을 그대로 가져다 쓰는 경우가 많았다.

예전에 한 번 린트를 전혀 사용하지 않고 프로젝트를 진행한 적이 있었는데,

그때는 코드 포맷이 조금만 어긋나도 직접 줄 맞추고, 띄어쓰기 하나하나 신경 쓰느라 꽤 스트레스를 받았다.

그 경험 덕분에 자동 줄바꿈과 포맷팅의 소중함을 제대로 느꼈다.

그래서 이번 프로젝트에서는 필수는 아니지만, 미래의 내가 이 글을 보고 다시 프로젝트를 세팅할 수 있기를 바라며 린트 설정 과정을 정리해두려고 한다.

eslint와 lint의 차이 ?

  • lint : 코드에서 문법 에러, 버그 가능성, 스타일 문제를 미리 잡아주는 검사 개념
    • 언어에 상관없이 사용할 수 있다!
  • eslint는 자바스크립트, 타입스크립트 용 lint 도구 중 하나이다. 즉, eslint ⊂ linter
  • eslint 특징
    • 규칙을 프로젝트에 맞게 아주 세밀하게 설정할 수 있다.
    • 자주사용하는 플러그인은 다음과 같다.
      • eslint-plugin-react
      • @typescript-eslint
      • eslint-config-airbnb

프로젝트 eslint 적용하기

1. lint-staged 설치

  npm i -D lint-staged

lint-staged는 커밋하려는 파일만 선택적으로 lint/format을 실행하는 라이브러리다.

  • 설치하는 이유 : 커밋할 파일이 1000개 있어도 staged된 파일만 검사해서 속도가 빠르다!

2. .husky/pre-commit 파일 만들기

커밋 전에 변경된 파일에 대해서만 린트를 실행하도록 설정한다!

   #!/bin/sh
   npx lint-staged
   

3. eslint.config.mjs 만들기


  import typescriptEslint from '@typescript-eslint/eslint-plugin'
  import typescriptParser from '@typescript-eslint/parser'
  import prettier from 'eslint-config-prettier'
  import globals from 'globals'
  
  export default [
    {
      ignores: ['.next/', 'out/', 'build/', 'next-env.d.ts'],
    },
    {
      files: ['**/*.{js,jsx,ts,tsx}'],
      languageOptions: {
        parser: typescriptParser,
        parserOptions: {
          ecmaVersion: 'latest',
          sourceType: 'module',
        },
      },
      plugins: {
        '@typescript-eslint': typescriptEslint,
      },
      rules: {
        ...typescriptEslint.configs.recommended.rules,
        'no-unused-vars': 'off',
        '@typescript-eslint/no-unused-vars': ['error'],
        'no-console': 'error',
      },
    },
  
    // 브라우저
    {
      files: ['**/*.{jsx,tsx}'],
      languageOptions: {
        globals: globals.browser,
      },
    },
  
    // Node / 서버
    {
      files: ['**/*.{js,ts}'],
      languageOptions: {
        globals: globals.node,
      },
    },
    prettier,
  ]
  

한 줄씩 알아보기

  • 프로젝트에서 타입스크립트를 사용할 예정이기 때문에 typescriptEslint, typescriptParserimport해야한다.

  • typescriptEslint → TypeScript 전용 ESLint 플러그인으로 TS 문법에 대한 규칙을 제공한다

  • typescriptParser → Eslint가 TypeScript 코드를 파싱할 수 있게 도와주는 파서이다. 이 설정이 없으면 eslint는 ts문법을 이해하지 못한다.

  • prettier → ESLint와 Prettier 충돌 방지용 설정을 도와준다! 포맷팅 관련 eslint 규칙은 전부 꺼준다.

  • export default → 배열의 각 객체가 하나의 설정 블록이다.

  • export default 작성순서
    1. eslint 검사에서 완전히 제외할 파일/폴더는 ignores배열에 넣어준다.

    - 해당 파일들은 린트를 돌릴 필요 없는 자동 생성 파일들이다.
      ```jsx
           {
             ignores: ['.next/', 'out/', 'build/', 'next-env.d.ts'],
           },
      ```
       - `.next/` : Next.js 빌드 결과물
       - `out/` : next export 결과
       - `build/` : 빌드 산출물
       - `next-env.d.ts` : Next가 자동 생성하는 타입 파일
        

    2. ESLint 검사 대상 파일 지정한다

    • JS / TS 관련 파일에 대해서만 린트를 실행한다.

         files: ['**/*.{js,jsx,ts,tsx}']

    3. languageOptions 을 설정한다

    • languageOptions 는 ESLint가 코드를 어떤 언어 환경으로 해석할지 여부이다.

            languageOptions: {
              parser: typescriptParser,
              parserOptions: {
                ecmaVersion: 'latest', // 최신 JS 문법 허용
                sourceType: 'module', // import / export 사용
              },
            },

    4. ESLint 규칙을 설정한다

                 rules: {
                   ...typescriptEslint.configs.recommended.rules, 
                   'no-unused-vars': 'off', // JS 기본 no-unused-vars은 끔 
                   '@typescript-eslint/no-unused-vars': 'error', // 사용하지 않는 변수 있으면 에러 처리
                   'no-console': 'error', // 콘솔로그 있으면 에러
                 },
    • no-unused-vars 를 끄는 이유
    • no-unused-vars순수 JavaScript 기준으로 만들어져, TypeScript 환경에서는 중복 오류를 발생하는 경우가 많다.
    • no-console 에러로 처리한 이유
      • 커밋 전에 잡지 않으면 로그가 쉽게 쌓이기 때문이다
      • 사용자에게는 토스트 등 UI로 예외를 전달할 예정이다

    5. 브라우저 설정한다

           {
             files: ['**/*.{jsx,tsx}'],
             languageOptions: {
               globals: globals.browser,
             },
           },
    • 리액트 컴포넌트처럼 브라우저에서 실행되는 코드에 대한 설정이다!

    • languageOptions을 통해 window ,document ,fetch , location 등을 전역 객체로 허용한다.

      ```jsx
      window.location.href // OK
      process.env.NODE_ENV // 에러
      ```

      6. Node / 서버 설정한다

        {
          files: ['**/*.{js,ts}'],
          languageOptions: {
            globals: globals.node,
          },
        },
  • API routes, 서버 컴포넌트, 설정 파일처럼 Node.js 환경에서 실행되는 코드들을 대상으로 한다.

    • Node 전역 객체(process, __dirname, Buffer)를 허용한다.

    eslint.config.mjs 에서 브라우저와 서버 설정을 나눈 이유

    • 실제 실행 환경이 다르기 때문에 섞이면 린트가 제대로 동작하지 않는다.

    • Next.js는 서버와 클라이언트 코드가 한 프로젝트 안에 공존한다.

    • 환경을 나누지 않으면 클라이언트 컴포넌트에서 잡아야 할 에러를 린트가 놓칠 수 있다.

      // 클라이언트 컴포넌트인데 이렇게 쓰면 에러나는데 린트는 통과된다. 
      console.log(process.env.SECRET_KEY)
      

4. pakage.json 설정하기

  • lint-staged 설치만 하면 아무 일도 안 일어난다

    ⇒ 어떤 파일에 어떤 명령을 실행할지 스스로 작성해야된다!

    그래서 package.json에 다음과 같이 설정을 추가했다.

    {
      "name": "hinkor",
      "version": "0.1.0",
      "private": true,
      "scripts": {
    /// 생략
      },
      "dependencies": {
    /// 생략
      },
      "devDependencies": {
    /// 생략
      },
      "lint-staged": {
        "**/*.{js,jsx,ts,tsx}": [
          "eslint --fix",
          "prettier --write"
        ],
        "**/*.{json,css,md}": [
          "prettier --write"
        ]
      }
    }
    
  • 커밋하려는 파일 중 →js, ts, tsx , jsx 가 있으면 자동으로 eslint 수정 → prettier 포맷

    • json, css, md 은 prettier만 실행된다
  • 이렇게 설정한 이유

    • 자바스크립트 / 타입스크립트 파일(js, ts, tsx 등)은 단순 포맷 문제뿐 아니라 문법 에러, 잠재적인 버그, 사용하지 않는 변수 같은 것들도 함께 잡아줘야 한다.

    • 반면에 json, css, md 파일은로직을 포함하지 않는 설정 파일 또는 문서 성격의 파일이기 때문에ESLint를 적용해도 얻는 이점이 거의 없다고 판단했다.

    • 따라서, 해당 파일들은 포맷팅만 책임지는 Prettier만 실행하도록 분리했다.

      ⇒ 로직이 있는 파일은 린트까지 통과해야 커밋하고, 그 외 파일은 포맷만 맞추고 빠르게 커밋하는 것을 목표로 했다

마치며

개인 프로젝트라도 이런 기본적인 규칙을 세워두는 게 생각보다 개발할 때 피로도를 많이 줄여준다는 걸 확실히 느꼈다!

profile
💡프론트엔드 공부 기록

0개의 댓글