Generated banner for Nuxt
Generated logo for Nuxt

Nuxt

Anthony FuCommunityCreated by Anthony Fu

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk

From the author

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.
— the description by Anthony Fu

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Nuxt is a full-stack Vue framework designed for universal deployment. This skill provides essential guidance on Nuxt 5, covering its directory structure, file-based routing, server-side rendering, and Nitro-based server engine. It includes best practices for data fetching, state management, and modular architecture, helping developers leverage features like auto-imports and hybrid rendering.

What it does

  • Explains the Nuxt 5 directory structure and configuration conventions.
  • Details file-based routing, including typed and case-sensitive routes.
  • Provides guidance on data fetching using useFetch and $fetch.
  • Covers server-side rendering, hybrid rendering, and Nitro server integration.
  • Explains how to extend applications using Nuxt Layers and modules.
  • Outlines lifecycle hooks for build-time and runtime customization.

When to use it

  • When starting a new full-stack project with Vue and Nuxt.
  • When migrating existing projects to Nuxt 5 and Nitro v3.
  • When implementing server-side rendering or hybrid rendering strategies.
  • When creating reusable application layers or custom Nuxt modules.
  • When optimizing data fetching patterns and state management in SSR environments.

Advantages

  • Provides standardized patterns for Nuxt 5 development.
  • Simplifies complex concepts like Nitro server integration and lifecycle hooks.
  • Offers clear guidance on project structure and file-based routing.
  • Supports efficient code reuse through the Nuxt Layers system.

Usage examples

“How do I structure my Nuxt 5 project directory?”
The agent explains the default app/ directory structure for Vue code and the root-level placement for server/, public/, and nuxt.config.ts files.
“Explain how to use layers to share components between projects.”
The agent provides instructions on using the extends property in nuxt.config.ts to import layers from local directories, git repositories, or npm packages.
“How do I hook into the page navigation lifecycle?”
The agent shows how to use nuxtApp.hook('page:finish', ...) within a plugin to execute code after navigation completes.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/antfu/nuxt/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip nuxt.zip -d .agents/skills

Documentation by Anthony Fu

Nuxt is a full-stack Vue framework that provides server-side rendering, file-based routing, auto-imports, and a powerful module system. It uses Nitro as its server engine for universal deployment across Node.js, serverless, and edge platforms.

The skill is based on Nuxt 5.x (5.0.0-0), generated at 2026-11-05.

Directory layout: the default srcDir is app/ — Vue app code (app.vue, components/, composables/, pages/, etc.) lives under app/, while server/, shared/, public/, modules/, layers/ and nuxt.config.ts stay at the project root. The ~/@ aliases point at app/; use ~~/@@ for the root.

Nuxt 5 note: Nuxt 5 makes many former experimental flags the default. The biggest shifts for agents: server code moves to Nitro v3 (h3 v2, Web-standard Request/Response), server utilities import from nuxt/server and are no longer auto-imported by default, $fetch/useFetch are typed from generated route types, routing is case-sensitive and typed by default, and the bundler is Vite 8 (Rolldown). Nuxt 5 requires Node 22.19/24.11+. A Nitro v2 compatibility layer (nitroLegacy) keeps older server code building during migration. See individual references for details.

Core

TopicDescriptionReference
Directory StructureNuxt 4 app/ srcDir, shared/, aliases, conventionscore-directory-structure
Configurationnuxt.config.ts, app.config.ts, aliases, Nuxt 5 defaults, jiti removal, experimentalcore-config
CLI CommandsDev server, build, generate, preview, and utility commandscore-cli
RoutingFile-based routing, case-sensitive + typed routes, named views, layout props, middlewarecore-routing
Data FetchinguseFetch, useAsyncData, $fetch, typed routes, createUseFetch factories, cachingcore-data-fetching
ModulesCreating and using Nuxt modules, Nuxt Kit utilitiescore-modules
DeploymentPlatform-agnostic deployment with Nitro, Vercel, Netlify, Cloudflarecore-deployment

Features

TopicDescriptionReference
Composables Auto-importsVue/Nuxt composables, custom composables, shared/, useAnnouncerfeatures-composables
Components Auto-importsComponent naming, lazy loading, hydration strategiesfeatures-components-autoimport
Built-in ComponentsNuxtLink, NuxtPage, NuxtLayout, NuxtAnnouncer, ClientOnly, and morefeatures-components
State ManagementuseState composable, SSR-friendly state, Pinia integrationfeatures-state
Server RoutesAPI routes, nuxt/server imports, Nitro v3 (h3 v2), sessions, middlewarefeatures-server

Rendering

TopicDescriptionReference
Rendering ModesUniversal (SSR), client-side (SPA), hybrid rendering, route rulesrendering-modes

Best Practices

TopicDescriptionReference
Data Fetching PatternsEfficient fetching, caching, parallel requests, error handlingbest-practices-data-fetching
SSR & HydrationAvoiding context leaks, hydration mismatches, composable patternsbest-practices-ssr

Advanced

TopicDescriptionReference
LayersExtending applications with reusable layersadvanced-layers
Lifecycle HooksBuild-time, runtime, and server hooksadvanced-hooks
Module AuthoringPublishable modules with Nuxt Kit, keyed composables, dependenciesadvanced-module-authoring

Files

references/advanced-module-authoring.md


name: module-authoring description: Complete guide to creating publishable Nuxt modules with best practices

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
      },
    })
  • Dev server handlers: addDevServerHandler takes the same variant keys (handler: { nuxt, nitro2, nitro3 }), plus method, middleware, meta. They run in the Nuxt process, so write nuxt as event => fn(event.req) against web Request/Response and never import nuxt/server there; nitro2 uses h3 v1 with toWebRequest(event) (add h3@^1 to dependencies). The object form needs @nuxt/kit@^4.6 in dependencies. Routes: nuxt/nitro3 match exactly, nitro2 matches as a prefix; end the route in /** to match subpaths on all (@nuxt/kit@^4.6.1). A nitro2 handler sees event.path relative to its route base.

  • Other variants: resolveServerVariant({ nuxt, nitro2 }) resolves the right file for aliases/type templates (or undefined); addServerImports({ from: { nuxt, nitro2 } }) accepts variants. nitro3 files may import nitro/* alone or alongside nuxt/server.

  • Detection: Nuxt infers the API from imports of registered files and their imports (import type ignored); override with meta.compatibility.server. Undetectable code is treated as nitro2. With nitropack v2 or nitro v3 hosts the nitro2/nitro3 variant is preferred over nuxt; to test the nuxt variant set server.builder to a non-Nitro builder (e.g. @nuxt/vite-server).

  • Server utilities taking the event should type it as RequestEvent from nuxt/server (or Pick<RequestEvent, 'req' | 'context'>), not H3Event. Type module runtime config by augmenting RuntimeConfig/PublicRuntimeConfig from nuxt/schema.

  • Plugins, storage, caching, tasks have no nuxt variant: register as nitro2 now, add nitro3 when porting (useKV from nitro/kv, defineTask from nitro/task). In Nitro v3, globalThis.$fetch is gone: use serverFetch(event, path) from nuxt/server.

  • 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',
      dependsOn: [], // optional: skip dev regeneration on file edits; only if output doesn't read watched file contents
      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,
    }

    // Or use kit utilities: extendViteConfig, extendWebpackConfig, extendRsbuildConfig(config => ...)
    // Plugins: addVitePlugin, addWebpackPlugin, addRsbuildPlugin, or addBuildPlugin({ vite, webpack, rsbuild, rspack })
    // addVitePlugin targets the app environments (client/ssr), never `nitro`; for the nitro Vite
    // environment push to nuxt.options.vite.plugins with applyToEnvironment: e => e.name === 'nitro'

    // 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 - https://nuxt.com/docs/guide/modules/recipes-advanced - https://nuxt.com/docs/api/kit/builder -->

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Anthony Fu
Publisher
Anthony Fu (Community)
Source
antfu/skills · skills/nuxt · commit f63e9ec
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Anthony Fu.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download nuxt.zip