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
Download the skill as a ZIP:
https://autogineer.com/agent-skills/antfu/nuxt/downloadUnzip 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
srcDirisapp/— Vue app code (app.vue,components/,composables/,pages/, etc.) lives underapp/, whileserver/,shared/,public/,modules/,layers/andnuxt.config.tsstay at the project root. The~/@aliases point atapp/; 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 fromnuxt/serverand are no longer auto-imported by default,$fetch/useFetchare typed from generated route types, routing is case-sensitive and typed by default, and the bundler is Vite 8 (Rolldown). Nuxt 5 requires Node22.19/24.11+. A Nitro v2 compatibility layer (nitroLegacy) keeps older server code building during migration. See individual references for details.
Core
| Topic | Description | Reference |
|---|---|---|
| Directory Structure | Nuxt 4 app/ srcDir, shared/, aliases, conventions | core-directory-structure |
| Configuration | nuxt.config.ts, app.config.ts, aliases, Nuxt 5 defaults, jiti removal, experimental | core-config |
| CLI Commands | Dev server, build, generate, preview, and utility commands | core-cli |
| Routing | File-based routing, case-sensitive + typed routes, named views, layout props, middleware | core-routing |
| Data Fetching | useFetch, useAsyncData, $fetch, typed routes, createUseFetch factories, caching | core-data-fetching |
| Modules | Creating and using Nuxt modules, Nuxt Kit utilities | core-modules |
| Deployment | Platform-agnostic deployment with Nitro, Vercel, Netlify, Cloudflare | core-deployment |
Features
| Topic | Description | Reference |
|---|---|---|
| Composables Auto-imports | Vue/Nuxt composables, custom composables, shared/, useAnnouncer | features-composables |
| Components Auto-imports | Component naming, lazy loading, hydration strategies | features-components-autoimport |
| Built-in Components | NuxtLink, NuxtPage, NuxtLayout, NuxtAnnouncer, ClientOnly, and more | features-components |
| State Management | useState composable, SSR-friendly state, Pinia integration | features-state |
| Server Routes | API routes, nuxt/server imports, Nitro v3 (h3 v2), sessions, middleware | features-server |
Rendering
| Topic | Description | Reference |
|---|---|---|
| Rendering Modes | Universal (SSR), client-side (SPA), hybrid rendering, route rules | rendering-modes |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| Data Fetching Patterns | Efficient fetching, caching, parallel requests, error handling | best-practices-data-fetching |
| SSR & Hydration | Avoiding context leaks, hydration mismatches, composable patterns | best-practices-ssr |
Advanced
| Topic | Description | Reference |
|---|---|---|
| Layers | Extending applications with reusable layers | advanced-layers |
| Lifecycle Hooks | Build-time, runtime, and server hooks | advanced-hooks |
| Module Authoring | Publishable modules with Nuxt Kit, keyed composables, dependencies | advanced-module-authoring |
Files
- ATTRIBUTION.mdAdded by AutoGineer889 B
- GENERATION.md134 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md4.5 KiB
- references/advanced-hooks.md7.2 KiB
- references/advanced-layers.md5.1 KiB
- references/advanced-module-authoring.md16.5 KiB
- references/best-practices-data-fetching.md9.2 KiB
- references/best-practices-ssr.md7.5 KiB
- references/core-cli.md4.8 KiB
- references/core-config.md10.5 KiB
- references/core-data-fetching.md11.4 KiB
- references/core-deployment.md5.1 KiB
- references/core-directory-structure.md9.8 KiB
- references/core-modules.md5.4 KiB
- references/core-routing.md6.8 KiB
- references/features-components-autoimport.md5.9 KiB
- references/features-components.md6.1 KiB
- references/features-composables.md7.2 KiB
- references/features-server.md11.1 KiB
- references/features-state.md3.9 KiB
- references/rendering-modes.md5.5 KiB
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.jsonModule 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 undernitropackv2, Nitro v3, and other builders with no h3/Nitro peer dependency. Leavenuxt/serverexternal when bundling.Type augmentation moved:
nitropack/types→nitro/types.Route rule types are declared on
h3/rulesnow (RouteRuleConfigfor config,RouteRulesfor runtime), notNitroRouteConfig/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/#importskeeps running via an automatic Nitro v2 compatibility layer (NUXT_B9003warning 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:
addDevServerHandlertakes the same variant keys (handler: { nuxt, nitro2, nitro3 }), plusmethod,middleware,meta. They run in the Nuxt process, so writenuxtasevent => fn(event.req)against webRequest/Responseand never importnuxt/serverthere;nitro2uses h3 v1 withtoWebRequest(event)(addh3@^1to dependencies). The object form needs@nuxt/kit@^4.6independencies. Routes:nuxt/nitro3match exactly,nitro2matches as a prefix; end the route in/**to match subpaths on all (@nuxt/kit@^4.6.1). Anitro2handler seesevent.pathrelative to its route base.Other variants:
resolveServerVariant({ nuxt, nitro2 })resolves the right file for aliases/type templates (orundefined);addServerImports({ from: { nuxt, nitro2 } })accepts variants.nitro3files may importnitro/*alone or alongsidenuxt/server.Detection: Nuxt infers the API from imports of registered files and their imports (
import typeignored); override withmeta.compatibility.server. Undetectable code is treated asnitro2. Withnitropackv2 ornitrov3 hosts thenitro2/nitro3variant is preferred overnuxt; to test thenuxtvariant setserver.builderto a non-Nitro builder (e.g.@nuxt/vite-server).Server utilities taking the event should type it as
RequestEventfromnuxt/server(orPick<RequestEvent, 'req' | 'context'>), notH3Event. Type module runtime config by augmentingRuntimeConfig/PublicRuntimeConfigfromnuxt/schema.Plugins, storage, caching, tasks have no
nuxtvariant: register asnitro2now, addnitro3when porting (useKVfromnitro/kv,defineTaskfromnitro/task). In Nitro v3,globalThis.$fetchis gone: useserverFetch(event, path)fromnuxt/server.Declare all real dependencies in
package.json— Nitro v3 no longer hoistsnitropackv2'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
Create module:
npx nuxi init -t module my-moduleDevelop:
npm run dev(runs playground)Build:
npm run buildTest:
npm run test
Best Practices
Use
createResolver(import.meta.url)for all path resolutionPrefix components to avoid naming conflicts
Make options type-safe with
ModuleOptionsinterfaceUse
moduleDependenciesinstead ofinstallModuleProvide sensible defaults for all options
Add compatibility requirements in
meta.compatibilityUse 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 or Anthony Fu.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download nuxt.zip