All skills
onmax avatar

/nuxt-modules

@22e13a9

Use when creating Nuxt modules: (1) Published npm modules (@nuxtjs/, nuxt-), (2) Local project modules (modules/ directory), (3) Runtime extensions (components, composables, plugins), (4) Server extensions (API routes, middleware), (5) Releasing/publishing modules to npm, (6) Setting up CI/CD workflows for modules. Provides defineNuxtModule patterns, Kit utilities, hooks, E2E testing, and release automation.

  • 4 files
  • 21.5 KB
  • MIT
  • Updated 8 months ago
  • GitHub

Use this Skill: https://skilld.dev/gh/onmax/claude-config/nuxt-modules

This session only. Nothing lands on disk.

referencesdevelopment.md

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

Module Development

Module anatomy, Kit utilities, and common patterns.

defineNuxtModule

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

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

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: '@nuxtjs/example',
    configKey: 'example',
    compatibility: { nuxt: '>=3.0.0' }
  },
  defaults: {
    apiKey: '',
    prefix: 'My'
  },
  hooks: {
    'app:error': err => console.error(err)
  },
  moduleDependencies: {
    '@nuxtjs/tailwindcss': {
      version: '>=6.0.0',
      optional: true,
      // Override nuxt.options for this module
      overrides: {},
      // Set defaults (lower priority than nuxt.options)
      defaults: {}
    }
  },
  // Or as async function (Nuxt 4.3+)
  async moduleDependencies(nuxt) {
    const needsSupport = nuxt.options.runtimeConfig.public?.feature
    return {
      '@nuxtjs/tailwindcss': needsSupport ? {} : { optional: true }
    }
  },
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)
    addPlugin(resolve('./runtime/plugin'))
  }
})

User configures via configKey:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/example'],
  example: { apiKey: 'xxx' }
})

Critical: #imports in Published Modules

Auto-imports don't work in node_modules. Runtime files must explicitly import:

// src/runtime/composables/useMyFeature.ts

// Wrong - won't work in published module
// Right - explicit import
import { useRoute } from '#imports'

const route = useRoute()
const route = useRoute()

Adding Plugins

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

export default defineNuxtModule({
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)
    addPlugin(resolve('./runtime/plugin'))
  }
})
// src/runtime/plugin.ts
import { defineNuxtPlugin } from '#imports'

export default defineNuxtPlugin((nuxtApp) => {
  return {
    provide: { myHelper: (msg: string) => console.log(msg) }
  }
})

Async plugins (Nuxt 4.3+): Lazy-load build plugins:

import { addVitePlugin, addWebpackPlugin } from '@nuxt/kit'

export default defineNuxtModule({
  async setup() {
    // Lazy-load only the bundler plugin needed
    addVitePlugin(() => import('my-plugin/vite').then(r => r.default()))
    addWebpackPlugin(() => import('my-plugin/webpack').then(r => r.default()))
  }
})

Adding Components

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

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

    // Single component
    addComponent({
      name: 'MyButton',
      filePath: resolve('./runtime/components/MyButton.vue'),
      // Custom declaration path (Nuxt 4.2+)
      declarationPath: resolve('./runtime/types/components.d.ts')
    })

    // Or entire directory with prefix
    addComponentsDir({
      path: resolve('./runtime/components'),
      prefix: 'My' // <MyButton>, <MyCard>
    })
  }
})

Adding Composables

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

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

    // Single or multiple
    addImports([
      { name: 'useAuth', from: resolve('./runtime/composables/useAuth') },
      { name: 'useUser', from: resolve('./runtime/composables/useUser') }
    ])

    // Or entire directory
    addImportsDir(resolve('./runtime/composables'))
  }
})

Adding Server Routes

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

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

    addServerHandler({
      route: '/api/_my-module/status',
      handler: resolve('./runtime/server/api/status.get')
    })
  }
})

Always prefix routes: /api/_my-module/ avoids conflicts.

Server imports (Nuxt 4.3+): Use #server alias in server files:

// runtime/server/api/users.ts
import { helper } from '#server/utils/helper'  // Clean imports

Runtime Config

export default defineNuxtModule({
  setup(options, nuxt) {
    // Public (client + server)
    nuxt.options.runtimeConfig.public.myModule = { apiUrl: options.apiUrl }

    // Private (server only)
    nuxt.options.runtimeConfig.myModule = { apiKey: options.apiKey }
  }
})

Lifecycle Hooks

export default defineNuxtModule({
  hooks: {
    'pages:extend': (pages) => {
      pages.push({ name: 'custom', path: '/custom', file: resolve('./runtime/pages/custom.vue') })
    }
  },
  setup(options, nuxt) {
    nuxt.hook('nitro:config', (nitroConfig) => {
      nitroConfig.prerender ||= {}
      nitroConfig.prerender.routes ||= []
      nitroConfig.prerender.routes.push('/my-route')
    })

    // Cleanup on close
    nuxt.hook('close', async () => {
      await cleanup()
    })
  }
})
Hook When
ready Nuxt initialized
modules:done All modules loaded
pages:extend Modify pages array
nitro:config Configure Nitro
close Nuxt shutting down

Custom Hooks

export interface ModuleHooks {
  'my-module:init': (config: MyConfig) => void
}

declare module '#app' {
  interface RuntimeNuxtHooks extends ModuleHooks {}
}

export default defineNuxtModule({
  setup(options, nuxt) {
    nuxt.hook('modules:done', async () => {
      await nuxt.callHook('my-module:init', { foo: 'bar' })
    })
  }
})

Virtual Files (Templates)

import { addTemplate, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    addTemplate({
      filename: 'my-module/config.mjs',
      getContents: () => `export const config = ${JSON.stringify(options)}`
    })
  }
})

Import: import { config } from '#build/my-module/config.mjs'

Type Declarations

import { addTypeTemplate, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    addTypeTemplate({
      filename: 'types/my-module.d.ts',
      getContents: () => `
        declare module '#app' {
          interface NuxtApp { $myHelper: (msg: string) => void }
        }
        export {}
      `
    })
  }
})

Logging & Errors

Use consola.withTag for consistent module logging:

import { consola } from 'consola'

const logger = consola.withTag('my-module')

export default defineNuxtModule({
  setup(options, nuxt) {
    logger.info('Initializing...')
    logger.warn('Deprecated option used')

    // Errors must include tag manually - consola doesn't add it
    if (!options.apiKey) {
      throw new Error('[my-module] `apiKey` option is required')
    }
  }
})

Disabling Modules

Set to false to disable (Nuxt 4.3+):

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss'],
  tailwindcss: false  // Disable the module
})

Disable inherited layer modules:

// nuxt.config.ts
export default defineNuxtConfig({
  extends: ['../base-layer'],
  disabledModules: ['@nuxt/image', '@sentry/nuxt/module']
})

Only works for modules from layers, not root project modules.

Local Modules

For project-specific modules:

// modules/my-local-module/index.ts
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: { name: 'my-local-module' },
  setup(options, nuxt) {
    // Module logic
  }
})
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['./modules/my-local-module']
})

Head Management (Nuxt 4.2+)

For modules that need to set global head elements:

import { setGlobalHead } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    setGlobalHead({
      title: 'My Module',
      meta: [{ name: 'description', content: 'Description' }],
      link: [{ rel: 'icon', href: '/favicon.ico' }]
    })
  }
})

Module Resolution (Nuxt 4.2+)

Resolve modules with custom extensions:

import { resolveModule } from '@nuxt/kit'

export default defineNuxtModule({
  async setup(options, nuxt) {
    const modulePath = await resolveModule('my-module', {
      extensions: ['.mjs', '.js', '.ts']
    })
  }
})

Quick Reference

Task Kit Function
Add plugin addPlugin()
Add component addComponent() / addComponentsDir()
Add composable addImports() / addImportsDir()
Add server route addServerHandler()
Add server utils addServerImports()
Virtual file addTemplate() / addServerTemplate()
Add types addTypeTemplate()
Add CSS nuxt.options.css.push()
Set global head setGlobalHead()
Resolve module resolveModule()

Resources

Source: SKILL.md on GitHub

No third-party reports yet.

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

Last checked against GitHub 9 hours ago.

Activeupdated 8 months ago

README badge

README badge for onmax/claude-config/nuxt-modules