Harmonizing Airbnb ESLint Rules with Nuxt 3's Composition API & TypeScript
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:
- Composition API (
<script setup>): Top-levelawait, implicit imports ofdefineProps/defineEmits, and the reactive nature of script setup variables can trigger “no-unused-vars” or “no-undef” errors. - TypeScript: Explicit typing requires different linting considerations, especially concerning type declarations, interfaces, and module augmentation.
- 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. - 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/recommendedmust be last to disable conflicting rules effectively.@nuxtjs/eslint-config-typescriptis vital for resolving Nuxt 3’s auto-imported composables.parser&parserOptions:vue-eslint-parseris the primary for.vuefiles, 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']: Configureseslint-plugin-importto correctly resolve TypeScript paths and aliases.rulesOverrides: Many Airbnb rules are adjusted here for Nuxt 3 and TypeScript compatibility. For instance, we delegateno-shadowandno-unused-varsto their@typescript-eslintcounterparts, which are type-aware.import/no-unresolvedis 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 forserver/apiroutes.
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
--cacheoption help speed up subsequent runs significantly. Ensure yournode_modules/.cachedirectory isn’t cleared unnecessarily. - Targeted Linting: For large projects, consider linting only changed files using Git hooks (e.g.,
lint-stagedwithhusky) 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
- “no-undef” for Nuxt Composables: If you’re seeing
no-undefforuseFetch,definePageMeta, etc., double-check that@nuxtjs/eslint-config-typescriptis correctly included in yourextendsarray. This module is designed to handle Nuxt 3’s auto-imports. module not foundfor TypeScript: VerifyparserOptions.projectin.eslintrc.cjscorrectly points to yourtsconfig.json. Incorrect paths are a common culprit.- Conflicting ESLint/Prettier Rules: Double-check that
plugin:prettier/recommendedis the last item in yourextendsarray. Its position is critical to disable all conflicting rules effectively. Error: You have used a rule which requires parserServices to be generated: This almost always meansparserOptions.projectis 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-eslintby correctly configuringparserOptions.projectto 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.jsonscripts and consider Git hooks (likelint-stagedwithhusky) 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:
- 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?
- Beyond code quality, how has a strong ESLint setup positively impacted collaboration and onboarding for new team members in your Nuxt projects?