ESLint and Code Conventions

Establish consistent code quality and style across your Nuxt project with ESLint, TypeScript rules, and automated formatting.

Why ESLint Matters

  • ✅ Catch bugs and errors early
  • ✅ Enforce consistent code style across team
  • ✅ Improve code readability and maintainability
  • ✅ Automate code quality checks
  • ✅ Integrate with CI/CD pipelines

Nuxt ESLint Setup

Install ESLint Module

npm install -D @nuxt/eslint-config eslint

Basic Configuration

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

ESLint Config File

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

export default withNuxt(
  // Your custom configs here
)

TypeScript ESLint Rules

Install TypeScript ESLint

npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

TypeScript Configuration

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

export default withNuxt({
  files: ['**/*.ts', '**/*.tsx', '**/*.vue'],
  languageOptions: {
    parser: '@typescript-eslint/parser',
    parserOptions: {
      project: './tsconfig.json'
    }
  },
  plugins: {
    '@typescript-eslint': require('@typescript-eslint/eslint-plugin')
  },
  rules: {
    // TypeScript specific rules
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-unused-vars': ['error', {
      argsIgnorePattern: '^_',
      varsIgnorePattern: '^_'
    }],
    '@typescript-eslint/consistent-type-imports': ['error', {
      prefer: 'type-imports'
    }],
    '@typescript-eslint/no-non-null-assertion': 'warn',
    '@typescript-eslint/ban-ts-comment': ['error', {
      'ts-expect-error': 'allow-with-description',
      'ts-ignore': false,
      'ts-nocheck': false
    }]
  }
})

Vue Specific Rules

Vue ESLint Plugin

npm install -D eslint-plugin-vue

Vue Configuration

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

export default withNuxt({
  files: ['**/*.vue'],
  rules: {
    // Vue component naming
    'vue/component-name-in-template-casing': ['error', 'PascalCase', {
      registeredComponentsOnly: false
    }],
    
    // Enforce component API style
    'vue/component-api-style': ['error', ['script-setup']],
    
    // Prop definitions
    'vue/require-prop-types': 'error',
    'vue/require-default-prop': 'error',
    'vue/prop-name-casing': ['error', 'camelCase'],
    
    // Template
    'vue/html-self-closing': ['error', {
      html: {
        void: 'always',
        normal: 'always',
        component: 'always'
      }
    }],
    'vue/max-attributes-per-line': ['error', {
      singleline: 3,
      multiline: 1
    }],
    'vue/first-attribute-linebreak': ['error', {
      singleline: 'ignore',
      multiline: 'below'
    }],
    
    // Directives
    'vue/v-on-event-hyphenation': ['error', 'always'],
    'vue/v-slot-style': ['error', 'shorthand'],
    
    // Composition API
    'vue/no-ref-as-operand': 'error',
    'vue/no-computed-properties-with-return': 'error',
    
    // Order
    'vue/order-in-components': ['error', {
      order: [
        'el',
        'name',
        'key',
        'parent',
        'functional',
        ['delimiters', 'comments'],
        ['components', 'directives', 'filters'],
        'extends',
        'mixins',
        ['provide', 'inject'],
        'ROUTER_GUARDS',
        'layout',
        'middleware',
        'validate',
        'scrollToTop',
        'transition',
        'loading',
        'inheritAttrs',
        'model',
        ['props', 'propsData'],
        'emits',
        'setup',
        'asyncData',
        'data',
        'fetch',
        'head',
        'computed',
        'watch',
        'watchQuery',
        'LIFECYCLE_HOOKS',
        'methods',
        ['template', 'render'],
        'renderError'
      ]
    }]
  }
})

Prettier Integration

Install Prettier

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

Prettier Configuration

// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "useTabs": false,
  "trailingComma": "none",
  "printWidth": 100,
  "arrowParens": "always",
  "endOfLine": "lf",
  "bracketSpacing": true,
  "vueIndentScriptAndStyle": false
}

Integrate with ESLint

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

export default withNuxt({
  extends: [
    'plugin:prettier/recommended'
  ],
  rules: {
    'prettier/prettier': ['error', {
      semi: false,
      singleQuote: true,
      trailingComma: 'none'
    }]
  }
})

Custom Rules for Nuxt

Enforce Auto-imports

export default withNuxt({
  rules: {
    // Enforce using auto-imported composables
    'no-restricted-imports': ['error', {
      paths: [{
        name: 'vue',
        importNames: ['ref', 'computed', 'watch', 'onMounted'],
        message: 'Use auto-imported composables from Nuxt instead.'
      }]
    }],
    
    // Prevent direct window access in SSR
    'no-restricted-globals': ['error', {
      name: 'window',
      message: 'Use import.meta.client check before accessing window.'
    }, {
      name: 'document',
      message: 'Use import.meta.client check before accessing document.'
    }]
  }
})

Enforce Naming Conventions

export default withNuxt({
  rules: {
    // File naming
    'unicorn/filename-case': ['error', {
      cases: {
        kebabCase: true,
        pascalCase: true
      }
    }],
    
    // Variable naming
    'camelcase': ['error', {
      properties: 'always',
      ignoreDestructuring: false
    }],
    
    // Component naming
    'vue/multi-word-component-names': ['error', {
      ignores: ['index', 'error', 'default']
    }]
  }
})

Import Order and Organization

export default withNuxt({
  plugins: ['import'],
  rules: {
    'import/order': ['error', {
      groups: [
        'builtin',
        'external',
        'internal',
        'parent',
        'sibling',
        'index'
      ],
      pathGroups: [
        {
          pattern: '~/**',
          group: 'internal'
        },
        {
          pattern: '@/**',
          group: 'internal'
        }
      ],
      'newlines-between': 'always',
      alphabetize: {
        order: 'asc',
        caseInsensitive: true
      }
    }],
    'import/no-duplicates': 'error',
    'import/no-unresolved': 'error',
    'import/newline-after-import': 'error'
  }
})

Package Scripts

// package.json
{
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "type-check": "nuxt typecheck",
    "prepare": "nuxt prepare"
  }
}

Git Hooks with Husky

Install Husky and lint-staged

npm install -D husky lint-staged
npx husky init

Configure Pre-commit Hook

// package.json
{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,yml}": [
      "prettier --write"
    ]
  }
}
# .husky/pre-commit
npm run lint-staged

Configure Commit Message

# .husky/commit-msg
npx --no -- commitlint --edit ${1}
// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat',
      'fix',
      'docs',
      'style',
      'refactor',
      'test',
      'chore',
      'perf',
      'ci',
      'build',
      'revert'
    ]]
  }
}

CI/CD Integration

GitHub Actions Workflow

# .github/workflows/lint.yml
name: Lint

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint
      
      - name: Check formatting
        run: npm run format:check
      
      - name: Type check
        run: npm run type-check

VSCode Configuration

Settings

// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  },
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue"
  ],
  "files.eol": "\n",
  "files.insertFinalNewline": true,
  "files.trimTrailingWhitespace": true
}
// .vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "Vue.volar",
    "Vue.vscode-typescript-vue-plugin"
  ]
}

Best Practices

✅ Do's

  1. ✅ Run linting before commits with hooks
  2. ✅ Configure IDE to auto-fix on save
  3. ✅ Document custom rules in README
  4. ✅ Use TypeScript strict mode
  5. ✅ Enforce consistent import order
  6. ✅ Review and update rules regularly
  7. ✅ Share configurations across projects

❌ Don'ts

  1. ❌ Don't disable rules without good reason
  2. ❌ Don't commit code with linting errors
  3. ❌ Don't mix different formatting styles
  4. ❌ Don't ignore TypeScript errors
  5. ❌ Don't skip CI linting checks
  6. ❌ Don't use any type excessively

Team Conventions

Code Review Checklist

## Code Review Checklist

- [ ] Code follows ESLint rules
- [ ] No TypeScript errors
- [ ] Proper component naming (PascalCase)
- [ ] Props have types and defaults
- [ ] Composables start with `use`
- [ ] No console.log in production code
- [ ] Comments for complex logic
- [ ] Tests added/updated
- [ ] Documentation updated

Naming Conventions

// ✅ Good Examples

// Components (PascalCase)
UserProfile.vue
DataTable.vue

// Composables (camelCase, start with 'use')
useAuth.ts
useDataTable.ts

// Utils (camelCase)
formatDate.ts
validateEmail.ts

// Types (PascalCase)
interface User {}
type UserRole = 'admin' | 'user'

// Constants (UPPER_SNAKE_CASE)
const API_BASE_URL = 'https://api.example.com'
const MAX_RETRY_COUNT = 3

// ❌ Bad Examples
userprofile.vue  // Should be PascalCase
auth.ts  // Composable should start with 'use'
FormatDate.ts  // Utils should be camelCase
user  // Type should be PascalCase
apiBaseUrl  // Constants should be UPPER_SNAKE_CASE

Advanced Configuration

Monorepo Setup

// eslint.config.mjs (root)
export default [
  {
    ignores: ['**/node_modules/**', '**/dist/**', '**/.nuxt/**']
  },
  ...withNuxt({
    files: ['apps/*/'],
    rules: {
      // App-specific rules
    }
  }),
  {
    files: ['packages/*/'],
    rules: {
      // Package-specific rules
    }
  }
]

Performance Optimization

// eslint.config.mjs
export default withNuxt({
  settings: {
    // Cache ESLint results
    cache: true,
    cacheLocation: '.eslintcache',
    
    // Ignore patterns
    ignorePatterns: [
      'dist',
      '.nuxt',
      'node_modules',
      '*.min.js'
    ]
  }
})

Consistent code conventions and automated linting improve code quality, reduce bugs, and make collaboration easier. Set up ESLint and Prettier early in your Nuxt project.

Progressive Web Apps (PWA)
Transform your Nuxt application into a Progressive Web App with offline support, installability, and native-like features.
Nuxt UI
Welcome to the Nuxt UI section! Learn how to build beautiful, accessible user interfaces quickly.
Files
Editor
Initializing WebContainer
Mounting files
Installing dependencies
Starting Nuxt server
Waiting for Nuxt to ready
Terminal