Index of every page on this site: llms.txt
All skills
antfu avatar

/nuxt

@7de210b official
by Anthony Fuantfu/skills6k stars
342
Run it yourself

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.

Use this Skill: https://skilld.dev/gh/antfu/skills/nuxt

Nothing lands on disk. Nothing to clean up.

Fork this Skill

Edit a local copy. It keeps the author and licence.

referencescore-data-fetching.md

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

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 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]
  },
})

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:

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 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 -->

Source: SKILL.md on GitHub

No rule matched.

skilld matched fixed text patterns in SKILL.md and file names. Patterns miss obfuscated code.

skilld run checks every file with the same patterns. It asks for approval before it loads a Skill with a behavior marked Needs approval.

No alerts3d5 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    This skill is a collection of documentation and reference materials for the Nuxt framework (version 5). It contains technical guides on routing, configuration, data fetching, and deployment, intended to assist an AI agent in working with Nuxt projects. No security risks were identified.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

  • Runlayer8mo

    3/20 files flagged

  • ZeroLeaks6mo

    Score: 93/100 · 2 sections analyzed

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

Last checked against GitHub last week.

Activeupdated last month
Other metadata
metadata
{
  "author": "Anthony Fu",
  "version": "2026.9.25",
  "source": "Generated from https://github.com/nuxt/nuxt, scripts located at https://github.com/antfu/skills"
}
  • Nuxt
  • Vue
  • ssr
  • full-stack
  • file-based-routing
  • nitro
  • auto-imports
  • server-routes
  • composables
  • state-management

README badge

README badge for antfu/skills/nuxt

This skill provides comprehensive guidance on Nuxt 3, a full-stack Vue framework with server-side rendering, file-based routing, auto-imports, and Nitro-powered server deployment. It covers directory structure, configuration, routing, data fetching, state management, server routes, rendering modes, and module authoring across core, feature, and advanced sections.

Generated from the current SKILL.md.

Does this skill cover Nuxt 2?
No. This skill is based on Nuxt 3.x, generated at 2026-01-28.
What server runtime does Nuxt use?
Nuxt uses Nitro as its server engine, which supports universal deployment across Node.js, serverless, and edge platforms.
Does this skill cover server routes and API endpoints?
Yes. The skill includes guidance on server routes, API routes, server middleware, and the Nitro server engine.
What rendering modes are supported?
The skill covers universal (SSR), client-side (SPA), hybrid rendering, and route rules for controlling how pages are rendered.
Does this skill explain state management?
Yes. It covers the useState composable, SSR-friendly state patterns, and Pinia integration.

Generated from the current SKILL.md. These answers refresh after source changes.