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://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/core-data-fetching.md


name: data-fetching description: useFetch, useAsyncData, and $fetch for SSR-friendly data fetching

Data Fetching

Nuxt provides composables for SSR-friendly data fetching that prevent double-fetching and handle hydration.

Overview

  • $fetch - Basic fetch utility (use for client-side events)

  • useFetch - SSR-safe wrapper around $fetch (use for component data)

  • useAsyncData - SSR-safe wrapper for any async function

  • createUseFetch / createUseAsyncData - factories (support addons) to build typed custom composables with baked-in defaults

On await

await does not change the server-rendered HTML — SSR always waits for the request and serializes the populated result. What it changes is client behavior:

  • With await (default): setup pauses until data is ready; client-side navigation is blocked until it resolves (user stays put, optionally with <NuxtLoadingIndicator>).

  • Without await: setup continues immediately, data starts at its default and fills in later; client navigation happens instantly and you handle status/error yourself.

Prefer the explicit lazy option (or useLazyFetch/useLazyAsyncData) for non-blocking intent. Note: await-ing a lazy call does not block client navigation — drop lazy if you want the wait.

useFetch

Primary composable for fetching data in components:

<script setup lang="ts">
const { data, status, error, refresh, clear } = await useFetch('/api/posts')
</script>

<template>
  <div v-if="status === 'pending'">Loading...</div>
  <div v-else-if="error">Error: {{ error.message }}</div>
  <div v-else>
    <article v-for="post in data" :key="post.id">
      {{ post.title }}
    </article>
  </div>
</template>
With Options
const { data } = await useFetch('/api/posts', {
  // Query parameters
  query: { page: 1, limit: 10 },
  // Request body (for POST/PUT)
  body: { title: 'New Post' },
  // HTTP method
  method: 'POST',
  // Only pick specific fields
  pick: ['id', 'title'],
  // Transform response
  transform: (posts) => posts.map(p => ({ ...p, slug: slugify(p.title) })),
  // Custom key for caching
  key: 'posts-list',
  // Don't fetch on server
  server: false,
  // Don't block navigation
  lazy: true,
  // Don't fetch immediately
  immediate: false,
  // Default value
  default: () => [],
})
Reactive Parameters
<script setup lang="ts">
const page = ref(1)
const { data } = await useFetch('/api/posts', {
  query: { page }, // Automatically refetches when page changes
})
</script>
Computed URL
<script setup lang="ts">
const id = ref(1)
const { data } = await useFetch(() => `/api/posts/${id.value}`)
// Refetches when id changes
</script>

useAsyncData

For wrapping any async function:

<script setup lang="ts">
const { data, error } = await useAsyncData('user', () => {
  return myCustomFetch('/user/profile')
})
</script>
Multiple Requests
<script setup lang="ts">
const { data } = await useAsyncData('cart', async () => {
  const [coupons, offers] = await Promise.all([
    $fetch('/api/coupons'),
    $fetch('/api/offers'),
  ])
  return { coupons, offers }
})
</script>

Custom Fetchers: createUseFetch / createUseAsyncData

Factory macros that produce a fully typed custom composable with pre-defined options. Must be an exported declaration inside app/composables/ (Nuxt injects dedup keys at build time).

// app/composables/useAPI.ts
export const useAPI = createUseFetch({
  baseURL: 'https://api.nuxt.com',
  // shared interceptors, headers, etc.
  onResponseError({ response }) {
    if (response.status === 401) navigateTo('/login')
  },
})
<script setup lang="ts">
// Same signature/return as useFetch, with defaults applied
const { data } = await useAPI('/modules')
// Caller can still override any option
const { data: other } = await useAPI('/modules', { baseURL: 'https://other.com' })
</script>

Default vs Override mode:

// Plain object → options act as DEFAULTS (caller can override)
export const useAPI = createUseFetch({ baseURL: '/api', lazy: true })

// Function → options OVERRIDE caller's (enforce auth/baseURL)
export const useAPI = createUseFetch(callerOptions => ({
  baseURL: 'https://api.nuxt.com', // always enforced
}))

Use the function form when you need useNuxtApp() (called in setup context, not module scope):

// app/composables/useAPI.ts
export const useAPI = createUseFetch(callerOptions => ({
  $fetch: useNuxtApp().$api as typeof $fetch,
  ...callerOptions,
}))

createUseAsyncData works identically for wrapping arbitrary async functions:

// app/composables/useCachedData.ts
export const useCachedData = createUseAsyncData({
  getCachedData(key, nuxtApp) {
    return nuxtApp.payload.data[key] ?? nuxtApp.static.data[key]
  },
})
Addons

Both factories accept addons: [...] built with defineUseFetchAddon / defineUseAsyncDataAddon (v4.6+). setup(options) runs before the call (declare custom typed call-site options via UseFetchAddonOptions<{ ... }> / UseAsyncDataAddonOptions<{ ... }>, may push to options.onRequest), and may return (asyncData) => ext | void that runs after, to attach watchers/cleanup and extend the returned object. Addons run in array order (first is the outermost middleware); duplicates run once.

const polling = defineUseAsyncDataAddon({
  setup: (options: UseAsyncDataAddonOptions<{ pollEvery?: number }>) => {
    if (import.meta.server || !options.pollEvery) { return }
    return (asyncData) => {
      const id = setInterval(() => asyncData.refresh(), options.pollEvery)
      onScopeDispose(() => clearInterval(id))
    }
  },
})
export const usePolledAsyncData = createUseAsyncData({ addons: [polling] })
// await usePolledAsyncData('key', () => $fetch('/api/x'), { pollEvery: 30_000 })

useAsyncData/useFetch also take a middleware: AsyncDataMiddleware[] option (v4.6+): (next, { signal }) => Promise functions wrapping the handler; must be consistent across calls sharing a key.

Related: onNuxtReady(cb, { timeout, signal }) runs a client-only callback when the app is ready and the browser idle (timeout caps the idle wait, signal skips it if aborted).

Replaces the old "don't await your custom useFetch wrapper" caveat — use these factories instead of hand-rolled wrappers.

Typed Routes (Nuxt 5)

$fetch and useFetch are typed from the routes your server builder reports (not nitro's InternalApi). The response is typed from the matching handler's return type, keyed by HTTP method:

// server/api/todos.get.ts
export default defineEventHandler(() => [{ id: '1', title: 'Buy milk' }])
const todos = await $fetch('/api/todos')
//    ^? { id: string, title: string }[]

If a handler validates its request, the body/query/headers are enforced on the call, and a method the route doesn't handle is rejected:

await $fetch('/api/todos', { method: 'POST', body: { title: 42 } })
//                                                  ^ not assignable to string

Key points:

  • The params option is removed from $fetch/useFetch (it was an alias for query) — move it to query.

  • A baseURL is resolved into the path before matching, so passing an already-prefixed path is an error.

  • A runtime path, absolute URL, or Request object resolves to unknown; provide an explicit type: $fetch<{ id: string }>(\/api/${x}`)`.

  • A path Nuxt doesn't recognize is unknown by default; set experimental.strictRouteTypes: true (or 'isomorphic') to make it an error.

  • Declare routes Nuxt can't see (runtime-registered, external APIs) by augmenting ServerRoutes:

// shared/server-routes.d.ts
import type { Endpoint } from 'nuxt/app'

declare module '@nuxt/schema' {
  interface ServerRoutes {
    '/api/hello': {
      [Endpoint]: { GET: { response: { message: string } } }
    }
  }
}

$fetch

For client-side events (form submissions, button clicks):

<script setup lang="ts">
async function submitForm() {
  const result = await $fetch('/api/submit', {
    method: 'POST',
    body: { name: 'John' },
  })
}
</script>

Important: Don't use $fetch alone in setup for initial data - it will fetch twice (server + client). Use useFetch or useAsyncData instead.

Return Values

All composables return:

PropertyTypeDescription
dataRef<T>Fetched data (undefined until resolved)
errorRef<Error>Error if request failed
statusRef<'idle' | 'pending' | 'success' | 'error'>Request status
pendingRef<boolean>Whether a request is in progress
refresh() => PromiseRefetch data
execute() => PromiseAlias for refresh
clear() => voidReset to default/idle and cancel pending requests

Prefer status over pending for fine-grained state. useFetch no longer accepts a top-level timeout option (still available on useAsyncData); use a cache option ('default', 'no-store', false, etc.) for Fetch cache control.

Lazy Fetching

Don't block navigation:

<script setup lang="ts">
// Using lazy option
const { data, status } = await useFetch('/api/posts', { lazy: true })

// Or use lazy variants
const { data, status } = await useLazyFetch('/api/posts')
const { data, status } = await useLazyAsyncData('key', fetchFn)
</script>

Refresh & Watch

<script setup lang="ts">
const category = ref('tech')

const { data, refresh } = await useFetch('/api/posts', {
  query: { category },
  // Auto-refresh when category changes
  watch: [category],
})

// Manual refresh
const refreshData = () => refresh()
</script>

Caching

useFetch generates its key from the URL plus the fetch options and the call's source location — so two useFetch('/api/user') calls in different components have different keys and each run their own request. To share data, pass the same explicit key:

<script setup lang="ts">
// In component A
const { data } = await useFetch('/api/user', { key: 'current-user' })

// In component B - uses cached data
const { data } = useNuxtData('current-user')
</script>

Refresh cached data globally:

// Refresh specific key
await refreshNuxtData('current-user')

// Refresh all data
await refreshNuxtData()

// Clear cached data
clearNuxtData('current-user')

Interceptors

const { data } = await useFetch('/api/auth', {
  onRequest({ options }) {
    options.headers.set('Authorization', `Bearer ${token}`)
  },
  onRequestError({ error }) {
    console.error('Request failed:', error)
  },
  onResponse({ response }) {
    // Process response
  },
  onResponseError({ response }) {
    if (response.status === 401) {
      navigateTo('/login')
    }
  },
})

Passing Headers (SSR)

useFetch automatically proxies cookies/headers from client to server. For $fetch:

<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
const data = await $fetch('/api/user', { headers })
</script>

<!-- Source references: - https://nuxt.com/docs/getting-started/data-fetching - https://nuxt.com/docs/api/composables/use-fetch - https://nuxt.com/docs/api/composables/use-async-data - https://nuxt.com/docs/api/composables/create-use-fetch - https://nuxt.com/docs/api/composables/create-use-async-data - https://nuxt.com/docs/guide/recipes/custom-usefetch - https://nuxt.com/docs/api/utils/define-use-fetch-addon - https://nuxt.com/docs/api/utils/define-use-async-data-addon - https://nuxt.com/docs/api/utils/on-nuxt-ready -->

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