Skip to content

useAsyncData Basic Usage

Learn how to use the type-safe composables generated by the CLI.

What the CLI Generates

For an OpenAPI endpoint like this:

yaml
/pets:
  get:
    operationId: getPets
    responses:
      '200':
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Pet'

The CLI generates two variants:

Standard Variant

typescript
// Generated: composables/useAsyncDataGetPets.ts
import { useApiAsyncData, type ApiAsyncDataOptions } from '../runtime/useApiAsyncData'

export const useAsyncDataGetPets = (
  params?: {},
  options?: ApiAsyncDataOptions<Pet[]>,
  customKey?: string  // Optional custom cache key
) => {
  // useApiAsyncData wraps Nuxt's useAsyncData under the hood
  return useApiAsyncData<Pet[]>('/pets', {
    method: 'GET',
    ...options
  }, customKey)
}

Raw Variant (with Headers)

typescript
// Generated: composables/useAsyncDataGetPetsRaw.ts
import { useApiAsyncDataRaw, type ApiAsyncDataOptions, type RawResponse } from '../runtime/useApiAsyncDataRaw'

export const useAsyncDataGetPetsRaw = (
  params?: {},
  options?: ApiAsyncDataOptions<RawResponse<Pet[]>>,
  customKey?: string
) => {
  // Returns full response with headers, status, and data
  return useApiAsyncDataRaw<Pet[]>('/pets', {
    method: 'GET',
    ...options
  }, customKey)
}

How Cache Keys Work

By default, generated composables auto-generate a cache key using operation name + resolved URL + params.

  • useAsyncDataGetPetById-/pet/1
  • useAsyncDataGetPetById-/pet/2
  • useAsyncDataFindPetsByStatus-/pet/findByStatus-{"status":"available"}
  • useAsyncDataGetInventory-/store/inventory (no params, so no suffix)

This gives independent entries for different URLs/params without extra work.

If you want intentional cache sharing, pass a custom key as the last parameter:

typescript
const { data } = useAsyncDataFindPetsByStatus(
  { status: 'available' },
  undefined,
  'mi-clave'
)

Basic Usage

Use the generated composable in your components:

vue
<script setup lang="ts">
// Type-safe composable generated by CLI
const { data: pets, pending, error } = useAsyncDataGetPets()
</script>

<template>
  <div v-if="pending">Loading...</div>
  <div v-else-if="error">Error: {{ error.message }}</div>
  <ul v-else>
    <li v-for="pet in pets" :key="pet.id">{{ pet.name }}</li>
  </ul>
</template>

Nuxt useAsyncData Documentation

Generated composables wrap Nuxt's useAsyncData. For complete documentation on data, pending, error, refresh(), cache keys, and standard options like immediate, watch, server, see:

Nuxt useAsyncData Official Documentation →

What the CLI Adds

1. Lifecycle Callbacks

Add hooks to intercept and react to request lifecycle events:

vue
<script setup lang="ts">
const { data: pets } = useAsyncDataGetPets(
  undefined,
  {
    // ⏱️ Before request
    onRequest: ({ url, headers }) => {
      console.log('Fetching from:', url)
      return {
        headers: {
          ...headers,
          'X-Request-ID': crypto.randomUUID()
        }
      }
    },
    
    // ✅ On success (2xx response)
    onSuccess: (pets) => {
      console.log(`Loaded ${pets.length} pets`)
      showToast('Pets loaded successfully', 'success')
    },
    
    // ❌ On error (4xx/5xx or network error)
    onError: (error) => {
      console.error('Failed to load pets:', error)
      if (error.status === 404) {
        showToast('No pets found', 'error')
      }
    },
    
    // 🏁 Always runs (success or error)
    onFinish: ({ success }) => {
      console.log('Request complete:', success ? 'success' : 'failed')
    }
  }
)
</script>

Learn more about callbacks →

2. Reactive Params — Auto-refresh on Change

CLI Addition — Not in Native Nuxt

Nuxt's native useAsyncData does not auto-detect reactive dependencies inside the fetch function. It requires you to manually declare watch sources, and even then it doesn't know how to re-evaluate the URL or params.

The generated composables solve this: pass a ref or computed as params and the composable wires everything up automatically.

Pass a ref or computed as params — when it changes, the composable re-fetches with the new values:

Path parameters:

vue
<script setup lang="ts">
const petId = ref(1)

// Reactively fetches /pet/1, then /pet/2 when petId changes
const { data: pet } = useAsyncDataGetPetById(
  computed(() => ({ petId: petId.value }))
)

function loadNext() {
  petId.value++  // triggers automatic re-fetch
}
</script>

Query parameters:

vue
<script setup lang="ts">
const status = ref('available')

// Re-fetches whenever status changes
const { data: pets } = useAsyncDataFindPetsByStatus(
  computed(() => ({ status: status.value }))
)
</script>

<template>
  <select v-model="status">
    <option>available</option>
    <option>pending</option>
    <option>sold</option>
  </select>
</template>

Disable auto-refresh if you want to control when the re-fetch happens:

vue
<script setup lang="ts">
const petId = ref(1)

const { data: pet, refresh } = useAsyncDataGetPetById(
  computed(() => ({ petId: petId.value })),
  { watch: false }  // petId changes won't trigger a re-fetch
)

async function loadPet(id: number) {
  petId.value = id
  await refresh()   // you decide when to fetch
}
</script>

Plain objects still work exactly as before — backward compatible:

typescript
// Works just as before — no reactivity, fetches once
const { data } = useAsyncDataGetPetById({ petId: 1 })

3. Pick

Select specific fields from the response without transform:

vue
<script setup lang="ts">
// Only get id, name, and status
const { data: pets } = useAsyncDataGetPets(
  undefined,
  {
    pick: ['id', 'name', 'status']
  }
)
// data is Ref<Array<{ id: number, name: string, status: string }>>
</script>

3. Response Headers (Raw Variant)

Access full response headers and status codes:

vue
<script setup lang="ts">
const { data: response } = useAsyncDataGetPetsRaw()

watch(response, (res) => {
  if (res) {
    console.log('Status:', res.status)           // 200
    console.log('Headers:', res.headers)         // Headers object
    console.log('Total:', res.headers.get('X-Total-Count'))
    console.log('Data:', res.data)               // Pet[]
  }
})
</script>

<template>
  <div>
    <p>Status: {{ response?.status }}</p>
    <p>Total Items: {{ response?.headers.get('X-Total-Count') }}</p>
    <ul>
      <li v-for="pet in response?.data" :key="pet.id">
        {{ pet.name }}
      </li>
    </ul>
  </div>
</template>

CLI Addition

Important: Nuxt's native useAsyncData does NOT return response headers or status codes. This is a feature added by the CLI's Raw variant.

Learn more about raw responses →

More Features

For additional features like transform, global headers, baseURL, and more, see Shared Features →

Examples with Callbacks

Simple GET Request

vue
<script setup lang="ts">
const { data: pets } = useAsyncDataGetPets(
  undefined,
  {
    onSuccess: (pets) => {
      console.log(`Loaded ${pets.length} pets`)
    }
  }
)
</script>

POST Request with Success Callback

vue
<script setup lang="ts">
const form = ref({ name: '', status: 'available' })

const { execute: createPet, pending } = useAsyncDataCreatePet(
  { body: form.value },
  {
    immediate: false, // Nuxt option: don't execute on mount
    onSuccess: (pet) => {
      showToast(`Created pet: ${pet.name}`, 'success')
      navigateTo(`/pets/${pet.id}`)
      form.value = { name: '', status: 'available' }
    },
    onError: (error) => {
      showToast(`Failed: ${error.message}`, 'error')
    }
  }
)
</script>

<template>
  <form @submit.prevent="createPet">
    <input v-model="form.name" placeholder="Pet name" />
    <button type="submit" :disabled="pending">Create</button>
  </form>
</template>

Request Interception

vue
<script setup lang="ts">
const { data: pets } = useAsyncDataGetPets(
  undefined,
  {
    onRequest: ({ headers, params }) => {
      return {
        headers: {
          ...headers,
          'X-Client-Version': '1.0.0'
        },
        params: {
          ...params,
          timestamp: Date.now()
        }
      }
    }
  }
)
</script>

Error Handling

vue
<script setup lang="ts">
const { data, error, status } = useAsyncDataGetPets()

// Different handling based on status
const errorMessage = computed(() => {
  if (!error.value) return null
  
  switch (error.value.status) {
    case 404:
      return 'No pets found'
    case 401:
      return 'Please log in'
    case 500:
      return 'Server error, please try again'
    default:
      return error.value.message
  }
})
</script>

<template>
  <div>
    <div v-if="status === 'pending'">Loading...</div>
    <div v-else-if="error" class="error">
      {{ errorMessage }}
    </div>
    <ul v-else>
      <li v-for="pet in data" :key="pet.id">{{ pet.name }}</li>
    </ul>
  </div>
</template>

SSR Considerations

vue
<script setup lang="ts">
// Runs on server and client
const { data } = useAsyncDataGetPets({}, {
  server: true
})

// Only runs on client
const { data: clientData } = useAsyncDataGetUserPets({}, {
  server: false
})
</script>

Pick Specific Fields

typescript
const { data: pets } = useAsyncDataGetPets(
  undefined,
  {
    pick: ['id', 'name', 'status']
  }
)

// Only id, name, and status are returned

Next Steps

Released under the Apache-2.0 License.