Nuxt ESLint + Prettier

Hyeon·2025년 7월 23일

Frontend

목록 보기
4/4
post-thumbnail

Nuxt에서 코딩 컨벤션을 위해 ESLint 와 Prettier를 설정하며 겪었던 과정입니다.

초기 ESLint 설정

처음에는 Nuxt에서 제공하는 ESLint 모듈이 있는지 몰라 일반적인 방법으로 ESLint를 설치했습니다.

pnpm add -D prettier eslint eslint-plugin-nuxt eslint-config-prettier

또한 ESLint가 v9로 업데이트되면서 설정파일이 기존에 .eslintrc 에서 eslint.config.js로 변경되었으며 Flat Config 시스템으로 변경되었습니다.

Error

기존에 사용하던 설정 파일을 사용하니 사용되지 않는 options라며 아래와 같은 오류가 많이 발생했습니다.

~~% npx eslint .

Oops! Something went wrong! :(

ESLint: 9.19.0

A config object is using the "root" key, which is not supported in flat config system.

Flat configs always act as if they are the root config file, so this key can be safely removed.

==========================================

A config object is using the "env" key, which is not supported in flat config system.

Flat config uses "languageOptions.globals" to define global variables for your files.

ESLint 9부터 env, parser, parserOptions가 Flat Config 시스템에서 지원되지 않으며,
대신 languageOptions.globals와 languageOptions.parser를 사용해야 합니다.

설정한 내용은 아래와 같습니다.

eslint.config.js

import globals from 'globals';
import tseslint from 'typescript-eslint';
import tsParser from '@typescript-eslint/parser';
import vueParser from 'vue-eslint-parser';
import vuePlugin from 'eslint-plugin-vue';
import tsEslintPlugin from '@typescript-eslint/eslint-plugin';
import prettierConfig from 'eslint-config-prettier';

export default {
  files: ['**/*.{vue,ts,js}'],
  languageOptions: {
    parser: vueParser,
    parserOptions: {
      parser: tsParser,
      sourceType: 'module',
    },
    globals: {
      ...globals.browser,
      ...globals.node,
      definePageMeta: 'readonly',
      defineNuxtConfig: 'readonly',
      defineNuxtPlugin: 'readonly',
      useRuntimeConfig: 'readonly',
      defineNuxtRouteMiddleware: 'readonly',
      navigateTo: 'readonly',
    },
  },
  plugins: {
    vue: vuePlugin,
    '@typescript-eslint': tsEslintPlugin,
  },
  rules: {
    ...tsEslintPlugin.configs.recommended.rules,
    ...tseslint.configs.recommended.rules,
    ...vuePlugin.configs['vue3-recommended'].rules,
    ...prettierConfig.rules,
    'vue/multi-word-component-names': 'off',
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-undef': 'warn',
    'no-unused-vars': 'warn',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-unused-vars': 'warn',
    '@typescript-eslint/no-unused-expressions': 'warn',
    // '@typescript-eslint/ban-types': 'warn',
    'no-empty': 'warn',
    '@typescript-eslint/no-empty-function': 'warn',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/naming-convention': [
      'warn',
      {
        selector: 'enum',
        format: ['PascalCase'],
      },
      {
        selector: 'function',
        format: ['camelCase'],
      },
      {
        selector: 'interface',
        format: ['PascalCase'],
        custom: {
          regex: '^I[A-Z]',
          match: true,
        },
      },
      {
        selector: 'typeLike',
        format: ['PascalCase'],
        custom: {
          regex: '^T[A-Z]',
          match: true,
        },
      },
      {
        selector: 'typeParameter',
        format: ['PascalCase'],
      },
      {
        selector: 'class',
        format: ['PascalCase'],
      },
    ],
  },
  ignores: [
    '**/node_modules/**',
    '**/.nuxt/**',
    '**/.output/**',
    '**/dist/**',
    '**/static/**',
    '**/public/**',
    '**/pnpm-lock.yaml',
    '**/*.log',
    '**/*.tmp',
    '**/.env',
    '**/.env.*',
  ],
};

🌀 Nuxt 전용 ESLint 모듈

나중에 Nuxt에서 공식적으로 제공하는 @nuxt/eslint 모듈이 있다는 걸 알게 됐습니다.

이 모듈은 Nuxt에 특화된 설정을 내장하고 있어, 따로 globals를 설정하지 않아도 됩니다.
https://eslint.nuxt.com/packages/module

설치 및 적용

pnpm add -D @nuxt/eslint eslint typescript

nuxt.config.ts에서 모듈 추가:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxt/eslint'
  ],
  eslint: {
    // options here
  }
})

커스텀 설정: eslint.config.mjs

withNuxt를 이용해 커스텀 세팅을 할 수 있습니다.
기존에 nuxt에서 eslint를 사용하기 위해 readonly로 세팅했던 옵션들을 정리할 수 있었습니다.

import withNuxt from './.nuxt/eslint.config.mjs';

export default withNuxt()
  .append({
    rules: {
      'no-undef': 'warn',
      'no-unused-vars': 'warn',
      'no-empty': 'warn',
      'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
      'vue/multi-word-component-names': 'off',
      'vue/attribute-hyphenation': 'off',
      'vue/html-self-closing': [
        'warn',
        {
          html: {
            void: 'always', // void elements (br, hr, img 등)는 항상 self-closing
            normal: 'never', // 일반 HTML elements는 self-closing 사용하지 않음
            component: 'always', // Vue components는 항상 self-closing
          },
          svg: 'always',
          math: 'always',
        },
      ],
    },
  })
  .override('nuxt/typescript/rules', {
    rules: {
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-unused-vars': 'warn',
      '@typescript-eslint/no-unused-expressions': 'warn',
      '@typescript-eslint/no-empty-function': 'warn',
      '@typescript-eslint/explicit-module-boundary-types': 'off',
      '@typescript-eslint/naming-convention': [
        'warn',
        {
          selector: 'enum',
          format: ['PascalCase'],
        },
        {
          selector: 'function',
          format: ['camelCase'],
        },
        {
          selector: 'interface',
          format: ['PascalCase'],
          custom: {
            regex: '^I[A-Z]',
            match: true,
          },
        },
        {
          selector: 'typeLike',
          format: ['PascalCase'],
          custom: {
            regex: '^T[A-Z]',
            match: true,
          },
        },
        {
          selector: 'typeParameter',
          format: ['PascalCase'],
        },
        {
          selector: 'class',
          format: ['PascalCase'],
        },
      ],
    },
  });

Prettier

.prettierrc

{
  "printWidth": 100,	// 한줄 최대 길이
  "tabWidth": 2,		// 기본 공백 크기
  "useTabs": false,		// 탭 대신 공백을 사용할지 여부
  "semi": true,			// 코드 끝 세미콜론
  "singleQuote": true,	// 싱글 쿼트(') 사용
  "quoteProps": "as-needed",	// 객체의 속성 이름에 쿼테이션("")을 사용할지
  "trailingComma": "es5",	// 객체나 배열 등에서 마지막 요소 뒤에 쉼표를 추가할지
  "bracketSpacing": true,	// 중괄호와 내용 사이의 공백
  "jsxBracketSameLine": false,		//
  "arrowParens": "always",		// 화살표 함수의 매개변수에 괄호를 추가할지
  "proseWrap": "preserve",		// 마크다운 파일에서 텍스트를 자동 줄바꿈할지
  "htmlWhitespaceSensitivity": "css",	//  HTML 파일에서 공백 처리를 설정
  "vueIndentScriptAndStyle": false,		//  Vue 파일의 <script><style> 태그를 들여쓰기
  "endOfLine": "auto"		// 줄바꿈 방식 설정
}

옵션을 알아보자

옵션설명예시/기본값
printWidth한 줄의 최대 길이80
tabWidth들여쓰기 공백 수2
semi세미콜론 사용 여부true (사용)
singleQuote싱글 쿼트(') 사용 여부false
quoteProps객체 속성 따옴표 처리as-needed
trailingComma마지막 쉼표 추가 여부es5
bracketSpacing중괄호와 내용 사이의 공백true
arrowParens화살표 함수 괄호 처리always
vueIndentScriptAndStyleVue 파일 script/style 들여쓰기false
endOfLine줄바꿈 방식auto (OS에 따라)

Prettier 적용:

prettier --ignore-path .eslintignore --write .

✅ 결론

  • Nuxt 3에서는 @nuxt/eslint 모듈 사용이 가장 간편하고 Nuxt 친화적입니다.
  • ESLint v9 이상부터는 eslint.config.js 기반의 Flat Config 구조를 따라야 합니다.
  • Prettier는 독립적으로 설정하되, ESLint와 충돌되지 않도록 eslint-config-prettier를 함께 사용하면 좋습니다.
  • 이 설정을 기반으로 팀 컨벤션을 유지하면, 코드 일관성과 안정성을 높일 수 있습니다

ref

https://eslint.org/
https://prettier.io/

0개의 댓글