← Back to blog

Harmonizing Airbnb ESLint Rules with Nuxt 3's Composition API & TypeScript

nuxtvuejavascriptwebdevfrontend

As we navigate 2026, Nuxt 3 has solidified its position as a powerhouse for full-stack Vue applications. Its robust TypeScript support, elegant Composition API, and rapid developer experience make building complex web applications a joy. Yet, with great power comes the responsibility of maintaining impeccable code quality and consistency across your projects.

ESLint is the indispensable linter that helps enforce coding standards, catch errors early, and improve readability. Among the myriad configurations, Airbnb’s JavaScript Style Guide stands as a gold standard, renowned for its rigor. However, integrating this opinionated guide with Nuxt 3’s modern paradigm—specifically <script setup> SFCs, powerful auto-imports, and deep TypeScript integration—isn’t always straightforward.

This post will guide you through seamlessly harmonizing Airbnb’s robust ESLint rules with your Nuxt 3 applications, ensuring your Composition API components and TypeScript code remain clean, consistent, and error-free. We’ll cover the necessary configurations, common gotchas, and best practices to keep your codebase pristine.

The Nuxt 3 Paradigm Shift vs. Airbnb’s Rigor

Airbnb’s ESLint configuration, while excellent, was primarily designed for vanilla JavaScript and React. Nuxt 3 introduces several paradigm shifts that initially clash with these rules:

  1. Composition API (<script setup>): Top-level await, implicit imports of defineProps/defineEmits, and the reactive nature of script setup variables can trigger “no-unused-vars” or “no-undef” errors.
  2. TypeScript: Explicit typing requires different linting considerations, especially concerning type declarations, interfaces, and module augmentation.
  3. Nuxt Auto-Imports: Nuxt 3’s magical auto-import for composables (e.g., useFetch, useState) means these functions aren’t explicitly imported, leading to “no-undef” issues.
  4. Vue-specific Syntax: Rules like requiring specific prop ordering or template structure aren’t inherently covered by a generic JavaScript config.

Our goal is to leverage Airbnb’s best practices while making necessary adjustments to embrace Nuxt 3’s modern development patterns.

Setting Up Your ESLint Powerhouse

Let’s dive into the practical setup. We’ll assume you have a fresh Nuxt 3 project.

Step 1: Install Dependencies

First, install ESLint, Prettier, the Airbnb configuration, and all necessary plugins for Vue, TypeScript, and Nuxt.

npm install --save-dev eslint prettier \
  eslint-config-airbnb-base eslint-plugin-import \
  @typescript-eslint/eslint-plugin @typescript-eslint/parser \
  eslint-plugin-vue eslint-config-prettier eslint-plugin-prettier \
  @nuxtjs/eslint-module

These packages provide: the core linter (eslint), code formatter (prettier), Airbnb’s rules (eslint-config-airbnb-base), TypeScript support (@typescript-eslint/*), Vue-specific rules (eslint-plugin-vue), Prettier integration (eslint-config-prettier, eslint-plugin-prettier), and essential Nuxt 3-specific rules (@nuxtjs/eslint-module).

Step 2: Configure .eslintrc.cjs

Create an .eslintrc.cjs file in your project root. Using .cjs ensures it runs correctly in a CommonJS context, common for build tools.

// .eslintrc.cjs
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2022: true, // For modern JS features
  },
  extends: [
    'airbnb-base', // The Airbnb base config
    'plugin:vue/vue3-recommended', // Vue 3 specific rules
    'plugin:@typescript-eslint/recommended', // TypeScript recommended rules
    '@nuxtjs/eslint-config-typescript', // Crucial for Nuxt 3 auto-imports
    'plugin:prettier/recommended', // Must be last to disable conflicting rules and add Prettier formatting
  ],
  parser: 'vue-eslint-parser', // Use vue-eslint-parser for parsing .vue files
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser', // Specify the TypeScript parser for scripts
    sourceType: 'module',
    project: ['./tsconfig.json'], // Required for type-aware linting, point to your tsconfig.json
    extraFileExtensions: ['.vue'],
  },
  plugins: [
    '@typescript-eslint',
    'vue',
    'prettier',
    'import', // For Airbnb's import rules
  ],
  settings: {
    'import/resolver': {
      typescript: true,
      node: true,
    },
  },
  rules: {
    'prettier/prettier': 'error', // Ensure Prettier rules are enforced as errors
    // Relax/override Airbnb rules for Nuxt 3 & TS compatibility
    'import/no-unresolved': 'off', // Nuxt 3 auto-imports handle this, so turn off for better DX
    'import/extensions': [
      'error',
      'ignorePackages',
      {
        js: 'never',
        jsx: 'never',
        ts: 'never',
        tsx: 'never',
        vue: 'never', // Allow omitting extensions for Vue files
      },
    ],
    'import/prefer-default-export': 'off', // Often not suitable for utility files or composables
    'no-shadow': 'off', // Handled by @typescript-eslint/no-shadow
    '@typescript-eslint/no-shadow': 'error', // Use TS version for shadowing
    'no-unused-vars': 'off', // Handled by @typescript-eslint/no-unused-vars
    '@typescript-eslint/no-unused-vars': [
      'warn', // Warn instead of error for unused vars
      {
        argsIgnorePattern: '^_', // Ignore variables starting with _
        varsIgnorePattern: '^_',
        caughtErrorsIgnorePattern: '^_',
      },
    ],
    '@typescript-eslint/explicit-module-boundary-types': 'off', // Too restrictive for many Nuxt use cases
    '@typescript-eslint/consistent-type-imports': 'error', // Encourage type-only imports
    // Vue specific overrides
    'vue/multi-word-component-names': 'off', // Allow single-word component names for pages/layouts
    'vue/attribute-hyphenation': 'off', // Allow camelCase for props in templates if preferred
    'vue/require-default-prop': 'off', // Often handled by TypeScript optional props
    'vue/html-self-closing': [
      'error',
      {
        html: {
          void: 'always',
          normal: 'always',
          component: 'always',
        },
        svg: 'always',
        math: 'always',
      },
    ],
    // Custom Nuxt specific rules (example)
    'no-restricted-syntax': [
      'error',
      {
        selector: 'CallExpression[callee.name="definePageMeta"]',
        message: 'Do not use definePageMeta outside of Nuxt pages.',
      },
    ],
  },
  // Overrides for files that should not be linted by TypeScript, or need specific rules
  overrides: [
    {
      files: ['**/*.vue'],
      rules: {
        'vue/component-api-style': ['error', ['script-setup']], // Enforce <script setup>
      },
    },
    {
      files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts', '**/*.vue'],
      parserOptions: {
        project: ['./tsconfig.json'], // Ensure TypeScript parser for these files
      },
    },
    {
      files: ['server/**/*.ts'], // For Nuxt server routes and API
      rules: {
        'import/prefer-default-export': 'off', // Server routes often have named exports
        'import/no-anonymous-default-export': 'off',
      },
    },
  ],
};

Key Configuration Insights:

  • extends: This array defines the base rule sets. Order matters: plugin:prettier/recommended must be last to disable conflicting rules effectively. @nuxtjs/eslint-config-typescript is vital for resolving Nuxt 3’s auto-imported composables.
  • parser & parserOptions: vue-eslint-parser is the primary for .vue files, delegating <script> blocks to @typescript-eslint/parser. project: ['./tsconfig.json'] is crucial for type-aware linting, enabling rules that understand your TypeScript types.
  • settings['import/resolver']: Configures eslint-plugin-import to correctly resolve TypeScript paths and aliases.
  • rules Overrides: Many Airbnb rules are adjusted here for Nuxt 3 and TypeScript compatibility. For instance, we delegate no-shadow and no-unused-vars to their @typescript-eslint counterparts, which are type-aware. import/no-unresolved is often disabled as Nuxt’s auto-imports handle many global references. Vue-specific rules (vue/multi-word-component-names) are relaxed to align with Nuxt conventions.
  • overrides: Allows applying specific rules or parser configurations to different file types or directories. This is useful for enforcing <script setup> or tailoring linting for server/api routes.

Step 3: Integrate with package.json

Add linting scripts to your package.json:

// package.json
{
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
    "lint": "eslint --ext .ts,.vue,.js,.cjs .",
    "lint:fix": "eslint --ext .ts,.vue,.js,.cjs . --fix",
    "format": "prettier --write --cache .",
    "lint:format": "npm run lint:fix && npm run format"
  }
}

Now you can run npm run lint to check your code, npm run lint:fix to auto-fix issues, and npm run format for Prettier. npm run lint:format is a powerful combination to ensure both linting and formatting standards are met.

Performance Considerations for Large Nuxt Projects

Type-aware linting, while incredibly powerful, can introduce performance overhead, especially in very large projects with extensive type definitions.

  • Caching: ESLint’s built-in caching and Prettier’s --cache option help speed up subsequent runs significantly. Ensure your node_modules/.cache directory isn’t cleared unnecessarily.
  • Targeted Linting: For large projects, consider linting only changed files using Git hooks (e.g., lint-staged with husky) before commits. This prevents full project scans on every change.
  • Editor Integration: Real-time feedback in modern IDEs like VS Code (with the ESLint extension) is often the most performant way to catch issues during development, making linting an invisible guardian.

Common Pitfalls & Troubleshooting

  1. “no-undef” for Nuxt Composables: If you’re seeing no-undef for useFetch, definePageMeta, etc., double-check that @nuxtjs/eslint-config-typescript is correctly included in your extends array. This module is designed to handle Nuxt 3’s auto-imports.
  2. module not found for TypeScript: Verify parserOptions.project in .eslintrc.cjs correctly points to your tsconfig.json. Incorrect paths are a common culprit.
  3. Conflicting ESLint/Prettier Rules: Double-check that plugin:prettier/recommended is the last item in your extends array. Its position is critical to disable all conflicting rules effectively.
  4. Error: You have used a rule which requires parserServices to be generated: This almost always means parserOptions.project is missing or incorrect, preventing type-aware rules from functioning.

Actionable Takeaways

  • Integrate Early: Set up ESLint and Prettier from the start of your Nuxt 3 project. It’s significantly easier than retrofitting standards into a sprawling, inconsistent codebase.
  • Leverage TypeScript-Aware Linting: Maximize @typescript-eslint by correctly configuring parserOptions.project to catch subtle type-related errors that regular linting misses.
  • Customize Thoughtfully: Airbnb’s rules are strict. Don’t hesitate to override specific rules that genuinely clash with Nuxt 3 conventions (like single-word component names for pages) or hinder your development flow. The goal is enhancement, not impediment.
  • Automate Linting & Formatting: Use package.json scripts and consider Git hooks (like lint-staged with husky) to ensure consistent code quality across your team automatically.
  • Utilize Editor Integration: Configure your IDE for real-time ESLint feedback. This makes linting a continuous, invisible guardian during development, catching issues as you type.

Harmonizing Airbnb ESLint rules with Nuxt 3’s cutting-edge features is a commitment to quality that significantly benefits maintainability, collaboration, and long-term project health. By carefully configuring your environment, you empower your team to write cleaner, more consistent, and ultimately more robust Nuxt applications.


Discussion Questions:

  1. What specific Airbnb ESLint rules did you find most challenging to adapt to Nuxt 3’s Composition API or auto-imports, and what was your eventual solution?
  2. Beyond code quality, how has a strong ESLint setup positively impacted collaboration and onboarding for new team members in your Nuxt projects?

Comments

No comments yet. Be the first!

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "OK", you consent to our use of cookies and agree to our Terms of Service & Privacy Policy.