Next.js에서 ESLint 9 설정

2025. 10. 8. 22:45·Frontend/Dev Practice
728x90
SMALL

기존 ESLint 8.x 버전이 2024년 4월에 지원이 공식적으로 종료(deprecated)되면서, 최신 ESLint 9으로의 마이그레이션이 필수적이 되었습니다. 그러나 ESLint 9에서는 기존 .eslintrc.js 방식 대신 새로운 Flat Config 포맷(eslint.config.mjs 또는 .js) 을 사용해야 하므로, 단순한 버전 업그레이드가 아닌 구조적 업데이트가 필요합니다.

또한 Prettier와 ESLint를 함께 사용할 경우 복잡했던 의존성 관리를 개선하기 위해, 이번 마이그레이션에서는 Prettier 대신 @stylistic/eslint-plugin (eslint-stylistic) 을 도입하여 코드 스타일 규칙을 ESLint 하나로 일원화했습니다.

💡 주의:
ESLint 9에서 .eslintrc 파일은 더 이상 지원되지 않습니다.
반드시 eslint.config.js 또는 eslint.config.mjs와 같은 Flat Config 파일을 사용해야 합니다.

주요 변경 사항 요약

ESLint 9은 단순한 메이저 업데이트가 아니라, 내부 구조와 설정 방식이 크게 바뀐 버전입니다.
다음은 주요 개선 및 변경 사항입니다.

  • Breaking changes: 일부 기본 룰(rule)의 동작이 변경되거나 제거됨
  • Deprecated rule 제거 및 대체
  • 플러그인 / 환경 설정 방식 개선
  • TypeScript 지원 강화
  • 성능 최적화 및 내부 구조 단순화

마이그레이션 전 점검 사항

ESLint 9를 도입하기 전에 다음 항목을 반드시 검토해야 합니다.

  1. 기존 ESLint 설정 및 플러그인 버전 확인
    → 각 플러그인이 ESLint 9를 지원하는지 확인합니다.
    (예: eslint-plugin-react, eslint-plugin-import, eslint-plugin-next, @typescript-eslint 등)
  2. Node.js 및 기타 패키지 호환성 점검
    → 사용 중인 Node.js 버전이 ESLint 9을 지원하는지 확인하고,
    typescript, babel, webpack 등의 devDependencies와 충돌이 없는지 검토합니다.
  3. 업그레이드 필요성 판단
    → 유지보수만 진행 중인 프로젝트라면 업그레이드 비용 대비 이점이 크지 않을 수 있습니다.
    반면, 지속적으로 개발이 이루어지는 프로젝트라면 최신 기능, 버그 수정, 보안 패치, 성능 개선을 위해 ESLint 9으로 전환하는 것이 좋습니다.

마이그레이션 방법

방법 1: @eslint/create-config 사용

npm install -D @eslint/create-config
npm init @eslint/config@latest

방법 2: 기존 설정을 Flat Config로 변환

기존 .eslintrc.json을 사용하는 경우, 자동 변환 도구를 활용할 수 있습니다.

npx @eslint/migrate-config .eslintrc.json

📘 참고: @eslint/migrate-config (npm)

방법 3: 완전 새로 설정 (추천)

기존 설정의 잔여 의존성과 호환성 문제를 방지하기 위해 처음부터 새로 구성하는 것을 추천드립니다.

1️⃣ 기존 ESLint / Prettier 관련 의존성 제거

package.json에서 다음 패키지들을 제거합니다.

  • eslint, eslint-plugin-, eslint-config-
  • @typescript-eslint/*
  • prettier, eslint-config-prettier, eslint-plugin-prettier
    이후 node_modules 폴더도 삭제합니다.

2️⃣ ESLint 9 및 관련 패키지 설치

npm i -D @eslint/eslintrc @eslint/js @stylistic/eslint-plugin eslint eslint-config-next eslint-plugin-simple-import-sort typescript-eslint
eslint ESLint 9 코어
@eslint/js 기본 JS 규칙 세트
@eslint/eslintrc 구버전 설정 호환용
eslint-config-next Next.js 전용 권장 설정
typescript-eslint TypeScript 파서 및 규칙 지원
eslint-plugin-simple-import-sort  import 순서 자동 정렬

3️⃣ eslint.config.mjs 파일 생성

ESLint 9의 Flat Config는 기본적으로 ES Module (ESM) 기반으로 작동합니다. 따라서 .mjs(mts) 확장자를 사용하거나, .js 파일에서 ESM 문법(import/export)을 사용해야 합니다.

import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import { dirname } from 'path';
import tseslint from 'typescript-eslint';
import { defineConfig } from 'eslint/config';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 💡 FlatCompat을 사용하여 기존 `.eslintrc` 기반 설정을 로드
const compat = new FlatCompat({
  baseDirectory: __dirname,
  recommendedConfig: js.configs.recommended,
  allConfig: js.configs.all,
});

export default defineConfig([
  js.configs.recommended,
  ...tseslint.configs.recommended,
  ...tseslint.configs.stylistic,
  ...compat.extends('next', 'next/core-web-vitals'),

  {
    ignores: ['build/*', 'dist/*', '**/.next/*', '**/node_modules/*'],
  },
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    rules: {
      'no-console': ['warn', { allow: ['info', 'debug', 'warn', 'error'] }],
      'no-debugger': 'warn',
      'no-fallthrough': 'error',
      // rules..
    },
  },
  {
    plugins: { '@stylistic': stylistic },
    rules: {
      '@stylistic/indent': ['error', 2],
      '@stylistic/quotes': ['error', 'single'],
      '@stylistic/semi': ['error', 'always'],
      '@stylistic/object-curly-spacing': ['error', 'always'],
      // rules...
    },
  },
]);

.eslintignore 파일은 deprecated되었으므로, 해당 내용을 eslint.config.mjs의 ignores 항목으로 옮긴 뒤 삭제합니다.

@stylistic/eslint-plugin을 활용하면 ESLint만으로 포매팅과 린트를 동시에 처리할 수 있습니다. 따라서 별도의 Prettier 설정 없이도 ESLint만으로 포맷팅이 가능합니다.

항목 Prettier eslint-stylistic
설정 파일 수 별도 .prettierrc 필요 ESLint 설정에 통합
의존성 수 10~15개 5~7개
자동 포맷 지원 지원
커스터마이즈 제한적 ESLint 규칙 단위로 조정 가능

 

Global 및 Local 규칙 지정

ESLint 9의 Flat Config는 설정 객체의 배열로 구성되며, 이 배열의 각 객체는 해당 객체에만 적용되는 로컬 규칙 또는 전역 규칙을 정의합니다.

로컬 규칙 (Local Rules)

files 속성을 사용하여 특정 파일에만 적용되는 규칙을 정의합니다.

// eslint.config.mjs 내
{
  // 린트를 적용할 파일을 지정합니다.
  files: ['app/**/*.ts', 'app/**/*.tsx'], 
  rules: {
    // 이 규칙은 'app/' 디렉토리의 TypeScript 파일에만 적용됩니다.
    'react/no-unescaped-entities': 'error', 
  },
},
{
  files: ['components/**/*.js'], 
  rules: {
    // 이 규칙은 'components/' 디렉토리의 JS 파일에만 적용됩니다.
    'prefer-const': 'off',
  },
}
전역 규칙 (Global Rules)

files 속성이 없는 설정 객체에 정의된 규칙은 기본적으로 모든 파일에 적용되는 전역 규칙으로 간주됩니다.

// eslint.config.mjs 내
{
  // files가 없으므로 모든 파일에 적용됩니다.
  rules: {
    'semi': ['error', 'always'],
    'no-console': 'warn', 
  },
},
// 또는 이전에 설정한 Next.js 및 ESLint 기본 설정
js.configs.recommended, // 전역 기본 규칙

4️⃣ VSCode 설정 추가

{
  "editor.codeActionsOnSave": {
    "source.fixAll": "never",
    "source.fixAll.eslint": "explicit"
  },
  "editor.defaultFormatter": "dbaeumer.vscode-eslint",
  "editor.formatOnSave": true,
  "eslint.validate": ["javascript", "typescript", "javascriptreact", "typescriptreact"],
  "editor.tabSize": 2
}

5️⃣ Lint 스크립트 추가

package.json에 추가합니다.

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

ESLint 실행 및 확인

// 특정 파일만 Lint
npm run lint eslint.config.mjs
npm run lint --fix eslint.config.mjs

// 모든 파일 Lint
npm run lint .
npm run lint --fix

🧩 트러블슈팅 FAQ

Q1. eslint-config-next가 ESLint 9에서 동작하지 않습니다.

eslint-config-next의 최신 버전(v14.1.0 이상) 이 Flat Config를 지원합니다. 만약 구버전을 사용해야 한다면, 반드시 본문에서 사용한 것처럼 @eslint/eslintrc의 FlatCompat을 사용하여 로드해야 호환성 문제를 피할 수 있습니다.

Q2. jiti 관련 오류가 발생합니다.

Oops! Something went wrong! :(

ESLint: 9.21.0

Error: You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features. 
at loadConfigFile (...\node_modules\eslint\lib\config\config-loader.js:178:19)

node version 이슈인지 jiti라이브러리를 설치해도 위 이슈가 계속 발생하는 경우가 있습니다. 설정 파일의 확장자를 .mts 대신 .mjs를 사용하여 Node.js의 기본 ESM 로딩 방식을 따르도록 시도해 볼 수 있습니다.

Q3. Prettier 속성을 eslint-stylistic 규칙으로 어떻게 변환하나요?

예를 들어, 기존에 아래와 같은 Prettier 설정을 사용하고 있었다면

{
  "arrowParens": "avoid",
  "bracketSameLine": false,
  "bracketSpacing": true,
  "endOfLine": "auto",
  "jsxSingleQuote": false,
  "printWidth": 80,
  "quoteProps": "as-needed",
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "useTabs": false,
}

이를 아래와 같은 스타일을 적용했습니다.

{
  plugins: {
    '@stylistic': stylistic,
  },
  rules: {
    '@stylistic/array-bracket-spacing': ['error', 'never'],
    '@stylistic/arrow-parens': ['error', 'as-needed'],
    '@stylistic/arrow-spacing': 'error',
    '@stylistic/brace-style': 'error',
    '@stylistic/comma-dangle': ['error', 'only-multiline'],
    '@stylistic/comma-spacing': ['error', { before: false, after: true }],
    '@stylistic/comma-style': ['error', 'last'],
    '@stylistic/dot-location': ['error', 'property'],
    '@stylistic/function-call-spacing': ['error', 'never'],
    '@stylistic/indent': ['error', 2, {
      SwitchCase: 1,
      flatTernaryExpressions: false,
    }],
    '@stylistic/jsx-closing-bracket-location': 'error',
    '@stylistic/jsx-curly-newline': 'error',
    '@stylistic/jsx-equals-spacing': ['error', 'never'],
    '@stylistic/jsx-one-expression-per-line': 'error',
    '@stylistic/jsx-pascal-case': ['error', { allowAllCaps: true }],
    '@stylistic/jsx-quotes': ['error'],
    '@stylistic/jsx-sort-props': ['error', { multiline: 'last', shorthandLast: true, callbacksLast: true }],
    '@stylistic/jsx-tag-spacing': ['error', { beforeSelfClosing: 'always' }],
    '@stylistic/jsx-wrap-multilines': ['error', {
      declaration: 'parens-new-line',
      assignment: 'parens-new-line',
      return: 'parens-new-line',
      arrow: 'parens-new-line',
      condition: 'parens-new-line',
      logical: 'parens-new-line',
      prop: 'parens-new-line'
    }],
    '@stylistic/key-spacing': ['error', { beforeColon: false }],
    '@stylistic/keyword-spacing': ['error', { before: true }],
    '@stylistic/linebreak-style': ['error', 'unix'],
    '@stylistic/member-delimiter-style': 'error',
    '@stylistic/new-parens': 'error',
    '@stylistic/newline-per-chained-call': ['error', { ignoreChainWithDepth: 2 }],
    '@stylistic/no-floating-decimal': 'error',
    '@stylistic/no-multi-spaces': 'error',
    '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
    '@stylistic/no-trailing-spaces': 'error',
    '@stylistic/no-whitespace-before-property': 'error',
    '@stylistic/object-curly-spacing': ['error', 'always'],
    '@stylistic/padded-blocks': ['error', 'never'],
    'padding-line-between-statements': [
      'error',
      { blankLine: 'always', prev: 'case', next: 'default' },
    ],
    '@stylistic/quote-props': ['error', 'as-needed'],
    '@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
    '@stylistic/rest-spread-spacing': ['error', 'never'],
    '@stylistic/semi-spacing': 'error',
    '@stylistic/semi': ['error', 'always'],
    '@stylistic/space-before-blocks': 'error',
    '@stylistic/space-before-function-paren': ['error', 'never'],
    '@stylistic/space-in-parens': ['error', 'never'],
    '@stylistic/template-curly-spacing': 'error',
    '@stylistic/type-annotation-spacing': 'error',
    '@stylistic/type-generic-spacing': ['error'],
  },
}

printWidth, useTabs 등 stylistic에 해당 속성이 존재하지만, 일부 자동 수정(auto-fix)이 적용되지 않는 속성들은 제외했습니다.

Q4. defineConfig를 사용하는 이유가 있나요?

ESLint 9부터는 설정 파일(eslint.config.js 또는 .mjs)이 기존의 정적인 JSON 방식에서 JavaScript 객체 배열을 사용하는 Flat Config 시스템으로 전환되었습니다.

 

이 변화는 설정 파일 자체가 코드로 동작함을 의미하며, 이 과정에서 타입 안정성과 개발 경험(DX) 향상이 중요해졌습니다. ESLint 팀이 제공하는 헬퍼 함수인 defineConfig()는 바로 이 목적을 위해 사용됩니다.

  • 자동 완성 (IntelliSense) 지원
  • 타입 안전성 보장
export default [/* configs */];

export default defineConfig([/* configs */]);

따라서 위 예제는 내부 동작은 동일합니다.

저작자표시 (새창열림)

'Frontend > Dev Practice' 카테고리의 다른 글

주석을 언제 작성해야 할까?  (3) 2024.10.27
빈 태그 사용을 피해야 하는 이유  (0) 2024.03.16
React Query에서 Non-null Assertion Operator 사용하기 좋은 방법일까?  (0) 2024.02.18
똑똑한 컴포넌트, 어디까지가 좋을까?  (0) 2024.01.26
왜 컴포넌트 안에서 new QueryClient 사용을 지양해야 할까?  (0) 2023.11.24
'Frontend/Dev Practice' 카테고리의 다른 글
  • 주석을 언제 작성해야 할까?
  • 빈 태그 사용을 피해야 하는 이유
  • React Query에서 Non-null Assertion Operator 사용하기 좋은 방법일까?
  • 똑똑한 컴포넌트, 어디까지가 좋을까?
끄적끄적 개발자
끄적끄적 개발자
생각나는 대로 끄적끄적, 코드 노트
  • 끄적끄적 개발자
    코딩을 끄적끄적
    끄적끄적 개발자
  • 전체
    오늘
    어제
    • 분류 전체보기 (47)
      • Frontend (43)
        • Tech Insight (24)
        • Dev Practice (7)
        • React Hook Form (4)
        • Module Federation (3)
        • Clone coding (5)
      • UIUX (2)
      • ETC (2)
  • 인기 글

  • 태그

    개발/코드품질
    개발/기술
    회고
    UI/UX
    개발/언어
    개발/정보
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
끄적끄적 개발자
Next.js에서 ESLint 9 설정
상단으로

티스토리툴바