Index of every page on this site: llms.txt
All skills
antfu avatar

/nuxt

@7de210b official
by Anthony Fuantfu/skills6k stars
342
Run it yourself

Nuxt full-stack Vue framework with SSR, auto-imports, and file-based routing. Use when working with Nuxt apps, server routes, useFetch, middleware, or hybrid rendering.

Use this Skill: https://skilld.dev/gh/antfu/skills/nuxt

Nothing lands on disk. Nothing to clean up.

Fork this Skill

Edit a local copy. It keeps the author and licence.

referencesadvanced-module-authoring.md

≈3.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Module Authoring

This guide covers creating publishable Nuxt modules with proper structure, type safety, and best practices.

Module Structure

Recommended structure for a publishable module:

my-nuxt-module/
├── src/
│   ├── module.ts          # Module entry
│   └── runtime/
│       ├── components/    # Vue components
│       ├── composables/   # Composables
│       ├── plugins/       # Nuxt plugins
│       └── server/        # Server handlers
├── playground/            # Development app
├── package.json
└── tsconfig.json

Module Definition

Basic Module with Type-safe Options

// src/module.ts
import { defineNuxtModule, createResolver, addPlugin, addComponent, addImports } from '@nuxt/kit'

export interface ModuleOptions {
  prefix?: string
  apiKey: string
  enabled?: boolean
}

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: 'my-module',
    configKey: 'myModule',
    compatibility: {
      nuxt: '>=3.0.0',
    },
  },
  defaults: {
    prefix: 'My',
    enabled: true,
  },
  setup(options, nuxt) {
    if (!options.enabled) return

    const { resolve } = createResolver(import.meta.url)

    // Module setup logic here
  },
})

Using .with() for Strict Type Inference

When you need TypeScript to infer that default values are always present:

import { defineNuxtModule } from '@nuxt/kit'

interface ModuleOptions {
  apiKey: string
  baseURL: string
  timeout?: number
}

export default defineNuxtModule<ModuleOptions>().with({
  meta: {
    name: '@nuxtjs/my-api',
    configKey: 'myApi',
  },
  defaults: {
    baseURL: 'https://api.example.com',
    timeout: 5000,
  },
  setup(resolvedOptions, nuxt) {
    // resolvedOptions.baseURL is guaranteed to be string (not undefined)
    // resolvedOptions.timeout is guaranteed to be number (not undefined)
  },
})

Adding Runtime Assets

Components

import { addComponent, addComponentsDir, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const { resolve } = createResolver(import.meta.url)

    // Single component
    addComponent({
      name: 'MyButton',
      filePath: resolve('./runtime/components/MyButton.vue'),
    })

    // Component directory with prefix
    addComponentsDir({
      path: resolve('./runtime/components'),
      prefix: 'My',
      pathPrefix: false,
    })
  },
})

Composables and Auto-imports

import { addImports, addImportsDir, addImportsSources, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const { resolve } = createResolver(import.meta.url)

    // Single import
    addImports({
      name: 'useMyUtil',
      from: resolve('./runtime/composables/useMyUtil'),
    })

    // Directory of composables
    addImportsDir(resolve('./runtime/composables'))

    // Auto-import from packages (Nuxt 4: accepts an array + package presets)
    addImportsSources([
      { package: '@vueuse/core' }, // all of the package's documented exports
      { from: 'my-lib', imports: ['useThing', 'useOther'] },
    ])
  },
})

Keyed Composables (SSR/client key injection)

If your composable needs a stable key shared between server and client (like useState/useAsyncData), register it so Nuxt's compiler injects a unique key when called with fewer than argumentLength args:

export default defineNuxtModule({
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)
    nuxt.options.optimization.keyedComposables.push({
      name: 'useMyState',
      source: resolve('./runtime/composables/state'), // exact source file (no barrels)
      argumentLength: 2,
    })
  },
})

The call must be statically analyzable and imported directly from source (barrel re-exports, dynamic/indirect calls, and reassignments are not transformed).

Plugins

import { addPlugin, addPluginTemplate, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options) {
    const { resolve } = createResolver(import.meta.url)

    // Static plugin file
    addPlugin({
      src: resolve('./runtime/plugins/myPlugin'),
      mode: 'client', // 'client', 'server', or 'all'
    })

    // Dynamic plugin with generated code
    addPluginTemplate({
      filename: 'my-module-plugin.mjs',
      getContents: () => `
import { defineNuxtPlugin } from '#app/nuxt'

export default defineNuxtPlugin({
  name: 'my-module',
  setup() {
    const config = ${JSON.stringify(options)}
    // Plugin logic
  }
})`,
    })
  },
})

Server Extensions

Server Handlers

import { addServerHandler, addServerScanDir, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const { resolve } = createResolver(import.meta.url)

    // Single handler
    addServerHandler({
      route: '/api/my-endpoint',
      handler: resolve('./runtime/server/api/my-endpoint'),
    })

    // Scan entire server directory (api/, routes/, middleware/, utils/)
    addServerScanDir(resolve('./runtime/server'))
  },
})

Server Composables

import { addServerImports, addServerImportsDir, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const { resolve } = createResolver(import.meta.url)

    // Single server import
    addServerImports({
      name: 'useServerUtil',
      from: resolve('./runtime/server/utils/useServerUtil'),
    })

    // Server composables directory
    addServerImportsDir(resolve('./runtime/server/composables'))
  },
})

Nitro Plugin

import { addServerPlugin, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const { resolve } = createResolver(import.meta.url)
    addServerPlugin(resolve('./runtime/server/plugin'))
  },
})
// runtime/server/plugin.ts (Nitro v3)
import { definePlugin } from 'nitro'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    console.log('Request:', event.url.pathname)
  })
})

Nuxt 5 / Nitro v3 for Module Authors

  • Portable server code: import from nuxt/server (ships in Nuxt 4.6+) so one file runs under nitropack v2, Nitro v3, and other builders with no h3/Nitro peer dependency. Leave nuxt/server external when bundling.

  • Type augmentation moved: nitropack/types → nitro/types.

  • Route rule types are declared on h3/rules now (RouteRuleConfig for config, RouteRules for runtime), not NitroRouteConfig/NitroRouteRules:

    declare module 'h3/rules' {
      interface RouteRuleConfig { myModule?: { enabled: boolean } }
      interface RouteRules { myModule?: { enabled: boolean } }
    }
  • Compatibility layer: a module whose server files still import h3/nitropack/#internal/nitro/#imports keeps running via an automatic Nitro v2 compatibility layer (NUXT_B9003 warning names it). To support Nuxt <4.6 and Nitro v3 at once, register both handler files:

    addServerHandler({
      route: '/api/my-module/status',
      handler: {
        nuxt: resolve('./runtime/server/status'),        // nuxt/server only
        nitro2: resolve('./runtime/server/status.legacy'), // h3 / nitropack
      },
    })
  • Declare all real dependencies in package.json — Nitro v3 no longer hoists nitropack v2's own deps into the server bundle.

Templates and Virtual Files

Generate Virtual Files

import { addTemplate, addTypeTemplate, addServerTemplate, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    // Client/build virtual file (accessible via #build/my-config.mjs)
    addTemplate({
      filename: 'my-config.mjs',
      getContents: () => `export default ${JSON.stringify(options)}`,
    })

    // Type declarations
    addTypeTemplate({
      filename: 'types/my-module.d.ts',
      getContents: () => `
declare module '#my-module' {
  export interface Config {
    apiKey: string
  }
}`,
    })

    // Nitro virtual file (accessible in server routes)
    addServerTemplate({
      filename: '#my-module/config.mjs',
      getContents: () => `export const config = ${JSON.stringify(options)}`,
    })
  },
})

Access Virtual Files

// In runtime plugin
// @ts-expect-error - virtual file
import config from '#build/my-config.mjs'

// In server routes
import { config } from '#my-module/config.js'

Extending Pages and Routes

import { extendPages, extendRouteRules, addRouteMiddleware, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const { resolve } = createResolver(import.meta.url)

    // Add pages
    extendPages((pages) => {
      pages.push({
        name: 'my-page',
        path: '/my-route',
        file: resolve('./runtime/pages/MyPage.vue'),
      })
    })

    // Add route rules (caching, redirects, etc.)
    extendRouteRules('/api/**', {
      cache: { maxAge: 60 },
    })

    // Add middleware
    addRouteMiddleware({
      name: 'my-middleware',
      path: resolve('./runtime/middleware/myMiddleware'),
      global: true,
    })
  },
})

Module Dependencies

Declare dependencies on other modules with version constraints:

export default defineNuxtModule({
  meta: {
    name: 'my-module',
  },
  moduleDependencies: {
    '@nuxtjs/tailwindcss': {
      version: '>=6.0.0',
      // Set defaults (user can override)
      defaults: {
        exposeConfig: true,
      },
      // Force specific options
      overrides: {
        viewer: false,
      },
    },
    '@nuxtjs/i18n': {
      optional: true, // Won't fail if not installed
      defaults: {
        defaultLocale: 'en',
      },
    },
  },
  setup() {
    // Dependencies are guaranteed to be set up before this runs
  },
})

Dynamic Dependencies

moduleDependencies(nuxt) {
  const deps: Record<string, any> = {
    '@nuxtjs/tailwindcss': { version: '>=6.0.0' },
  }

  if (nuxt.options.ssr) {
    deps['@nuxtjs/html-validator'] = { optional: true }
  }

  return deps
}

Lifecycle Hooks

Requires meta.name and meta.version:

export default defineNuxtModule({
  meta: {
    name: 'my-module',
    version: '1.2.0',
  },
  onInstall(nuxt) {
    // First-time setup
    console.log('Module installed for the first time')
  },
  onUpgrade(nuxt, options, previousVersion) {
    // Version upgrade migrations
    console.log(`Upgrading from ${previousVersion}`)
  },
  setup(options, nuxt) {
    // Regular setup runs every build
  },
})

Extending Configuration

export default defineNuxtModule({
  setup(options, nuxt) {
    // Add CSS
    nuxt.options.css.push('my-module/styles.css')

    // Add runtime config
    nuxt.options.runtimeConfig.public.myModule = {
      apiUrl: options.apiUrl,
    }

    // Extend Vite config
    nuxt.options.vite.optimizeDeps ||= {}
    nuxt.options.vite.optimizeDeps.include ||= []
    nuxt.options.vite.optimizeDeps.include.push('some-package')

    // Add build transpile
    nuxt.options.build.transpile.push('my-package')
  },
})

Using Hooks

export default defineNuxtModule({
  // Declarative hooks
  hooks: {
    'components:dirs': (dirs) => {
      dirs.push({ path: '~/extra' })
    },
  },

  setup(options, nuxt) {
    // Programmatic hooks
    nuxt.hook('pages:extend', (pages) => {
      // Modify pages
    })

    nuxt.hook('imports:extend', (imports) => {
      imports.push({ name: 'myHelper', from: 'my-package' })
    })

    nuxt.hook('nitro:config', (config) => {
      // Modify Nitro config
    })

    nuxt.hook('vite:extendConfig', (config) => {
      // Modify Vite config
    })
  },
})

Path Resolution

import { createResolver, resolvePath, findPath } from '@nuxt/kit'

export default defineNuxtModule({
  async setup(options, nuxt) {
    // Resolver relative to module
    const { resolve } = createResolver(import.meta.url)

    const pluginPath = resolve('./runtime/plugin')

    // Resolve with extensions and aliases
    const entrypoint = await resolvePath('@some/package')

    // Find first existing file
    const configPath = await findPath([
      resolve('./config.ts'),
      resolve('./config.js'),
    ])
  },
})

Module Package.json

{
  "name": "my-nuxt-module",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/module.mjs",
      "require": "./dist/module.cjs"
    }
  },
  "main": "./dist/module.cjs",
  "module": "./dist/module.mjs",
  "types": "./dist/types.d.ts",
  "files": ["dist"],
  "scripts": {
    "dev": "nuxi dev playground",
    "build": "nuxt-module-build build",
    "prepare": "nuxt-module-build build --stub"
  },
  "dependencies": {
    "@nuxt/kit": "^4.0.0"
  },
  "devDependencies": {
    "@nuxt/module-builder": "latest",
    "nuxt": "^4.0.0"
  }
}

Disabling Modules

Users can disable a module via config key:

// nuxt.config.ts
export default defineNuxtConfig({
  // Disable entirely
  myModule: false,

  // Or with options
  myModule: {
    enabled: false,
  },
})

Development Workflow

  1. Create module: npx nuxi init -t module my-module
  2. Develop: npm run dev (runs playground)
  3. Build: npm run build
  4. Test: npm run test

Best Practices

  • Use createResolver(import.meta.url) for all path resolution
  • Prefix components to avoid naming conflicts
  • Make options type-safe with ModuleOptions interface
  • Use moduleDependencies instead of installModule
  • Provide sensible defaults for all options
  • Add compatibility requirements in meta.compatibility
  • Use virtual files for dynamic configuration
  • Separate client/server plugins appropriately
<!-- Source references: - https://nuxt.com/docs/api/kit/modules - https://nuxt.com/docs/api/kit/components - https://nuxt.com/docs/api/kit/autoimports - https://nuxt.com/docs/api/kit/plugins - https://nuxt.com/docs/api/kit/templates - https://nuxt.com/docs/api/kit/nitro - https://nuxt.com/docs/api/kit/pages - https://nuxt.com/docs/api/kit/resolving - https://nuxt.com/docs/guide/modules/recipes-basics - https://nuxt.com/docs/guide/modules/module-dependencies - https://nuxt.com/docs/guide/modules/server-compatibility -->

Source: SKILL.md on GitHub

No rule matched.

skilld matched fixed text patterns in SKILL.md and file names. Patterns miss obfuscated code.

skilld run checks every file with the same patterns. It asks for approval before it loads a Skill with a behavior marked Needs approval.

No alerts3d5 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    This skill is a collection of documentation and reference materials for the Nuxt framework (version 5). It contains technical guides on routing, configuration, data fetching, and deployment, intended to assist an AI agent in working with Nuxt projects. No security risks were identified.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

  • Runlayer8mo

    3/20 files flagged

  • ZeroLeaks6mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 7de210b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated last month
Other metadata
metadata
{
  "author": "Anthony Fu",
  "version": "2026.9.25",
  "source": "Generated from https://github.com/nuxt/nuxt, scripts located at https://github.com/antfu/skills"
}
  • Nuxt
  • Vue
  • ssr
  • full-stack
  • file-based-routing
  • nitro
  • auto-imports
  • server-routes
  • composables
  • state-management

README badge

README badge for antfu/skills/nuxt

This skill provides comprehensive guidance on Nuxt 3, a full-stack Vue framework with server-side rendering, file-based routing, auto-imports, and Nitro-powered server deployment. It covers directory structure, configuration, routing, data fetching, state management, server routes, rendering modes, and module authoring across core, feature, and advanced sections.

Generated from the current SKILL.md.

Does this skill cover Nuxt 2?
No. This skill is based on Nuxt 3.x, generated at 2026-01-28.
What server runtime does Nuxt use?
Nuxt uses Nitro as its server engine, which supports universal deployment across Node.js, serverless, and edge platforms.
Does this skill cover server routes and API endpoints?
Yes. The skill includes guidance on server routes, API routes, server middleware, and the Nitro server engine.
What rendering modes are supported?
The skill covers universal (SSR), client-side (SPA), hybrid rendering, and route rules for controlling how pages are rendered.
Does this skill explain state management?
Yes. It covers the useState composable, SSR-friendly state patterns, and Pinia integration.

Generated from the current SKILL.md. These answers refresh after source changes.