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
}
Recommended Extensions
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin"
]
}
Best Practices
✅ Do's
- ✅ Run linting before commits with hooks
- ✅ Configure IDE to auto-fix on save
- ✅ Document custom rules in README
- ✅ Use TypeScript strict mode
- ✅ Enforce consistent import order
- ✅ Review and update rules regularly
- ✅ Share configurations across projects
❌ Don'ts
- ❌ Don't disable rules without good reason
- ❌ Don't commit code with linting errors
- ❌ Don't mix different formatting styles
- ❌ Don't ignore TypeScript errors
- ❌ Don't skip CI linting checks
- ❌ Don't use
anytype 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.