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.

referencesbest-practices-data-fetching.md

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

Data Fetching Best Practices

Effective data fetching patterns for SSR-friendly, performant Nuxt applications.

Choose the Right Tool

Scenario Use
Component initial data useFetch or useAsyncData
User interactions (clicks, forms) $fetch
Third-party SDK/API useAsyncData with custom function
Multiple parallel requests useAsyncData with Promise.all
Reusable API client with shared defaults createUseFetch factory

Await vs Non-Await Usage

The await keyword controls whether data fetching blocks navigation:

With await - Blocking Navigation

<script setup lang="ts">
// Navigation waits until data is fetched (uses Vue Suspense)
const { data } = await useFetch('/api/posts')
// data.value is available immediately after this line
</script>
  • Server: Fetches data and includes it in the payload
  • Client hydration: Uses payload data, no re-fetch
  • Client navigation: Blocks until data is ready

Without await - Non-Blocking (Lazy)

<script setup lang="ts">
// Navigation proceeds immediately, data fetches in background
const { data, status } = useFetch('/api/posts', { lazy: true })
// data.value may be undefined initially - check status!
</script>

<template>
  <div v-if="status === 'pending'">Loading...</div>
  <div v-else>{{ data }}</div>
</template>

Equivalent to using useLazyFetch:

<script setup lang="ts">
const { data, status } = useLazyFetch('/api/posts')
</script>

When to Use Each

Pattern Use Case
await useFetch() Critical data needed for SEO/initial render
useFetch({ lazy: true }) Non-critical data, better perceived performance
await useLazyFetch() Same as lazy, await only ensures initialization

Avoid Double Fetching

❌ Wrong: Using $fetch Alone in Setup

<script setup lang="ts">
// This fetches TWICE: once on server, once on client
const data = await $fetch('/api/posts')
</script>

✅ Correct: Use useFetch

<script setup lang="ts">
// Fetches on server, hydrates on client (no double fetch)
const { data } = await useFetch('/api/posts')
</script>

Use Explicit Cache Keys

❌ Avoid: Auto-generated Keys

<script setup lang="ts">
// Key is auto-generated from file/line - can cause issues
const { data } = await useAsyncData(() => fetchPosts())
</script>

✅ Better: Explicit Keys

<script setup lang="ts">
// Explicit key for predictable caching
const { data } = await useAsyncData(
  'posts',
  () => fetchPosts(),
)

// Dynamic keys for parameterized data
const route = useRoute()
const { data: post } = await useAsyncData(
  `post-${route.params.id}`,
  () => fetchPost(route.params.id),
)
</script>

Handle Loading States Properly

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

<template>
  <div v-if="status === 'pending'">
    <SkeletonLoader />
  </div>
  <div v-else-if="error">
    <ErrorMessage :error="error" />
  </div>
  <div v-else>
    <PostList :posts="data" />
  </div>
</template>

Use Lazy Fetching for Non-critical Data

<script setup lang="ts">
const id = useRoute().params.id

// Critical data - blocks navigation
const { data: post } = await useFetch(`/api/posts/${id}`)

// Non-critical data - doesn't block navigation
const { data: comments, status } = useFetch(`/api/posts/${id}/comments`, {
  lazy: true,
})

// Or use useLazyFetch
const { data: related } = useLazyFetch(`/api/posts/${id}/related`)
</script>

<template>
  <article>
    <h1>{{ post?.title }}</h1>
    <p>{{ post?.content }}</p>
  </article>

  <section v-if="status === 'pending'">Loading comments...</section>
  <CommentList v-else :comments="comments" />
</template>

Minimize Payload Size

Use pick for Simple Filtering

<script setup lang="ts">
const { data } = await useFetch('/api/users', {
  // Only include these fields in payload
  pick: ['id', 'name', 'avatar'],
})
</script>

Use transform for Complex Transformations

<script setup lang="ts">
const { data } = await useFetch('/api/posts', {
  transform: (posts) => {
    return posts.map(post => ({
      id: post.id,
      title: post.title,
      excerpt: post.content.slice(0, 100),
      date: new Date(post.createdAt).toLocaleDateString(),
    }))
  },
})
</script>

Parallel Fetching

Fetch Independent Data with useAsyncData

<script setup lang="ts">
const { data } = await useAsyncData(
  'dashboard',
  async (_nuxtApp, { signal }) => {
    const [user, posts, stats] = await Promise.all([
      $fetch('/api/user', { signal }),
      $fetch('/api/posts', { signal }),
      $fetch('/api/stats', { signal }),
    ])
    return { user, posts, stats }
  },
)
</script>

Multiple useFetch Calls

<script setup lang="ts">
// These run in parallel automatically
const [{ data: user }, { data: posts }] = await Promise.all([
  useFetch('/api/user'),
  useFetch('/api/posts'),
])
</script>

Efficient Refresh Patterns

Watch Reactive Dependencies

<script setup lang="ts">
const page = ref(1)
const category = ref('all')

const { data } = await useFetch('/api/posts', {
  query: { page, category },
  // Auto-refresh when these change
  watch: [page, category],
})
</script>

Manual Refresh

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

async function refreshPosts() {
  await refresh()
}
</script>

Conditional Fetching

<script setup lang="ts">
const userId = ref<string | null>(null)

const { data, execute } = useFetch(() => `/api/users/${userId.value}`, {
  immediate: false, // Don't fetch until userId is set
})

// Later, when userId is available
function loadUser(id: string) {
  userId.value = id
  execute()
}
</script>

Server-only Fetching

<script setup lang="ts">
// Only fetch on server, skip on client navigation
const { data } = await useFetch('/api/static-content', {
  server: true,
  lazy: true,
  getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key],
})
</script>

Error Handling

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

// Watch for errors if need event-like handling
watch(error, (err) => {
  if (err) {
    console.error('Fetch failed:', err)
    // Show toast, redirect, etc.
  }
}, { immediate: true })
</script>

<template>
  <div v-if="error">
    <p>Failed to load: {{ error.message }}</p>
    <button @click="refresh()">Retry</button>
  </div>
</template>

Shared Data Across Components

<!-- ComponentA.vue -->
<script setup lang="ts">
const { data } = await useFetch('/api/user', { key: 'current-user' })
</script>

<!-- ComponentB.vue -->
<script setup lang="ts">
// Access cached data without refetching
const { data: user } = useNuxtData('current-user')

// Or refresh it
const { refresh } = await useFetch('/api/user', { key: 'current-user' })
</script>

Centralize API Config with createUseFetch

Instead of hand-rolling a wrapper around useFetch (and worrying about whether to await it), use the createUseFetch factory. It produces a fully typed composable with shared baseURL, headers, and interceptors:

// app/composables/useAPI.ts
export const useAPI = createUseFetch({
  baseURL: 'https://api.nuxt.com',
  onRequest({ options }) {
    const { session } = useUserSession()
    if (session.value?.token) {
      options.headers.set('Authorization', `Bearer ${session.value.token}`)
    }
  },
  async onResponseError({ response }) {
    if (response.status === 401) await navigateTo('/login')
  },
})
<script setup lang="ts">
const { data: profile } = await useAPI('/me') // auth + 401 handling baked in
</script>

For lower-level control, create a custom $fetch instance in a plugin and wrap it with useAsyncData (or pass it to createUseFetch) — this avoids double fetching during SSR:

// app/plugins/api.ts
export default defineNuxtPlugin(() => {
  const api = $fetch.create({ baseURL: 'https://api.nuxt.com' })
  return { provide: { api } }
})
<script setup lang="ts">
const { $api } = useNuxtApp()
const { data } = await useAsyncData('modules', () => $api('/modules'))
</script>

Avoid useAsyncData for Side Effects

❌ Wrong: Side Effects in useAsyncData

<script setup lang="ts">
// Don't trigger Pinia actions or side effects
await useAsyncData(() => store.fetchUser()) // Can cause issues
</script>

✅ Correct: Use callOnce for Side Effects

<script setup lang="ts">
await callOnce(async () => {
  await store.fetchUser()
})
</script>
<!-- Source references: - https://nuxt.com/docs/4.x/getting-started/data-fetching - https://nuxt.com/docs/4.x/api/composables/use-fetch - https://nuxt.com/docs/4.x/api/composables/use-async-data - https://nuxt.com/docs/4.x/api/composables/use-lazy-fetch - https://nuxt.com/docs/4.x/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.