Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dvlkit/nuxe/llms.txt

Use this file to discover all available pages before exploring further.

Nuxe provides two composables for fetching data: useAsyncData for wrapping any arbitrary async logic, and useFetch as a convenience layer that combines useAsyncData with the built-in $fetch HTTP client. Both composables run on the server during SSR, serialize the result into the page payload, and rehydrate it on the client — so the network request is never made twice.

useAsyncData

useAsyncData accepts a unique cache key, an async handler function, and an optional options object. It returns a set of reactive refs you can use directly in your template.

Signature

useAsyncData<T>(
  key: string | Ref<string> | (() => string),
  handler: () => Promise<T>,
  options?: UseAsyncDataOptions<T>
): UseAsyncDataReturn<T>
The return type UseAsyncDataReturn<T> contains:
data
Ref<T | null>
The resolved value from the handler. null until the handler completes (or until default is applied).
pending
Ref<boolean>
true while the handler is in-flight.
error
Ref<Error | null>
Populated if the handler throws or all retries are exhausted.
status
Ref<'idle' | 'pending' | 'success' | 'error'>
Lifecycle status of the async operation. See Status values below.
refresh
() => Promise<void>
Manually re-run the handler and update data, pending, error, and status.

Basic example

<script setup lang="ts">
const { data, pending, error, refresh } = useAsyncData('my-key', () =>
  $fetch('/api/ping')
)
</script>

<template>
  <div>
    <p v-if="pending">Loading…</p>
    <p v-else-if="error">Error: {{ error.message }}</p>
    <pre v-else>{{ data }}</pre>
    <button @click="refresh">Refresh</button>
  </div>
</template>

Options

default
() => T | Ref<T>
Factory function that returns the initial (fallback) value for data before the handler resolves. Useful for avoiding null checks in templates.
server
boolean
default:"true"
Set to false to skip running the handler on the server. The fetch will then happen on the client after hydration.
lazy
boolean
default:"false"
When true, the handler runs without blocking navigation. pending starts as true and the data arrives asynchronously. Useful for non-critical data that shouldn’t delay page rendering.
retryCount
number
default:"0"
Number of times to retry the handler after a failure before populating error.
retryDelayMs
number
default:"0"
Milliseconds to wait between retry attempts.
watch
WatchSource | WatchSource[]
One or more reactive sources. Whenever any of them change, useAsyncData automatically re-runs the handler and refreshes data.

Status values

The status ref follows a simple lifecycle:
ValueMeaning
'idle'Handler has not been triggered yet.
'pending'Handler is currently running.
'success'Handler completed and data is populated.
'error'Handler failed; error contains the thrown value.

Hydration

When a page is server-rendered, useAsyncData stores its result in the server payload under the provided key. On the client, the matching hydrated value is read back immediately — the handler is not re-executed. This means every key must be unique across the application. If the same key is reused with a different handler, the client will receive stale data.

useFetch

useFetch is a thin wrapper around useAsyncData and $fetch. It derives its cache key from the URL (or options.key), handles reactive options automatically, and exposes an extra statusCode ref for the HTTP response status.

Signature

useFetch<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>
): UseFetchReturn<T>
UseFetchReturn<T> extends UseAsyncDataReturn<T> with:
statusCode
Ref<number | null>
The HTTP status code of the most recent response. null if no response has been received yet or a network-level error occurred.

Basic example

<script setup lang="ts">
interface PingResponse {
  message: string
  timestamp: number
  query: string | null
}

const { data, pending, statusCode } = useFetch<PingResponse>('/api/ping', {
  query: { q: 'hello' },
})
</script>

<template>
  <p v-if="pending">Loading…</p>
  <p v-else>{{ data?.message }} (HTTP {{ statusCode }})</p>
</template>

Reactive query with watch

Options like query, params, body, and headers accept plain values, refs, or getters. When the value of any of these changes, useFetch automatically triggers a refresh — no manual watch required for most cases. For ref-based inputs you can be explicit with watch:
<script setup lang="ts">
import { ref } from 'vue'

const queryInput = ref('')

const { data, pending } = useFetch('/api/ping', {
  key: 'fetch-demo-query',
  query: { q: queryInput },
  watch: [queryInput],
})
</script>

<template>
  <input v-model="queryInput" placeholder="Type to refetch…" />
  <p v-if="pending">Fetching…</p>
  <p v-else>Echoed: {{ data?.query ?? '(empty)' }}</p>
</template>

Error handling with onError

<script setup lang="ts">
const { data, error, statusCode } = useFetch('/api/ping', {
  key: 'fetch-demo-error',
  query: { status: 500 },
  onError({ error }) {
    console.error(`Request failed with ${error.status}: ${error.message}`)
  },
})
</script>

Additional options

useFetch accepts all UseAsyncDataOptions fields plus:
key
string
Override the cache key. Required when url is a function.
method
string
HTTP method ('GET', 'POST', etc.). Accepts a ref or getter.
baseURL
string | Ref<string>
Base URL prepended to relative request URLs. On the server, Nuxe falls back to runtimeConfig.app.baseURL, then NUXE_BASE_URL, then http://localhost:3000 when this is omitted.
query
Record<string, unknown> | Ref<...>
Query string parameters appended to the URL. Accepts a plain object or a reactive ref — changes trigger an automatic refresh.
params
Record<string, unknown> | Ref<...>
Alias for query.
body
unknown | Ref<unknown>
Request body for POST/PUT/PATCH requests. Accepts a reactive ref.
headers
HeadersInit | Ref<HeadersInit>
Additional request headers. Accepts a reactive ref.
onResponse
(ctx: FetchContext & { response: FetchResponse<T> }) => void | Promise<void>
Called after every successful response. Receives the full fetch context including the raw Response object.
onError
(ctx: { error: UseFetchError }) => void | Promise<void>
Called when the request fails (network error or non-2xx response). error.status and error.statusText are populated for HTTP errors.
When url is a function (e.g. a getter that computes the URL dynamically), options.key is required. Nuxe cannot derive a stable cache key from a function reference, so omitting key throws an error at runtime: [nuxe] useFetch: options.key is required when url is a function.
Pass lazy: true to prevent a slow fetch from blocking navigation. The page renders immediately with pending === true and the data fills in once the request resolves. This is ideal for below-the-fold content or secondary data that isn’t needed for the initial paint.

$fetch and createFetch

$fetch is the low-level HTTP client available in both components and server routes. It is re-exported by Nuxe with one enhancement: on the server, relative URLs are automatically prefixed with the application’s base URL (read from runtimeConfig.app.baseURL, then NUXE_BASE_URL, then http://localhost:3000) so server-to-server calls to your own API routes work without any configuration. It is built on ofetch.
// Works on the server and on the client — no base URL needed
const data = await $fetch('/api/ping')

// Also works for external URLs
const repo = await $fetch('https://api.github.com/repos/dvlkit/nuxe')
Use $fetch directly when you need fire-and-forget mutations or when you want full control over the request without the reactive overhead of useFetch. createFetch creates a new pre-configured $fetch instance. Use it to share a base URL, default headers, or interceptors across many requests without repeating options on every call.
import { createFetch } from '@dvlkit/nuxe'

const apiFetch = createFetch({ baseURL: 'https://api.example.com' })

// Every call inherits the base URL
const user = await apiFetch('/users/1')
import { defineEventHandler, readBody } from '@dvlkit/nuxe/server'

export default defineEventHandler(async (event) => {
  const { name } = await readBody<{ name: string }>(event)
  return { message: `Hello, ${name}!` }
})

Build docs developers (and LLMs) love