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/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 functioncreateUseFetch/createUseAsyncData- factories (supportaddons) 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,datastarts at its default and fills in later; client navigation happens instantly and you handlestatus/erroryourself.
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
useFetchwrapper" 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 stringKey points:
The
paramsoption is removed from$fetch/useFetch(it was an alias forquery) — move it toquery.A
baseURLis resolved into the path before matching, so passing an already-prefixed path is an error.A runtime path, absolute URL, or
Requestobject resolves tounknown; provide an explicit type:$fetch<{ id: string }>(\/api/${x}`)`.A path Nuxt doesn't recognize is
unknownby default; setexperimental.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:
| Property | Type | Description |
|---|---|---|
data | Ref<T> | Fetched data (undefined until resolved) |
error | Ref<Error> | Error if request failed |
status | Ref<'idle' | 'pending' | 'success' | 'error'> | Request status |
pending | Ref<boolean> | Whether a request is in progress |
refresh | () => Promise | Refetch data |
execute | () => Promise | Alias for refresh |
clear | () => void | Reset to default/idle and cancel pending requests |
Prefer
statusoverpendingfor fine-grained state.useFetchno longer accepts a top-leveltimeoutoption (still available onuseAsyncData); use acacheoption ('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