new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Nuxt · Getting started · all subjects

data-fetching

66 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Nuxt provides data-fetching utilities

Nuxt provides composables for handling server-side rendering compatible data fetching with different strategies.

$fetch uses ofetch library

$fetch is auto-imported globally across a Nuxt application and is an alias for the ofetch library. It is the simplest way to make network requests.

Three data-fetching composables and utilities in Nuxt

Nuxt provides $fetch, useFetch, and useAsyncData for data fetching. $fetch is the simplest way to make network requests. useFetch is a wrapper around $fetch that fetches data only once in universal rendering. useAsyncData is similar to useFetch but offers more fine-grained control.

Why useFetch and useAsyncData are needed instead of just $fetch

Using only $fetch in Vue component setup can cause data to be fetched twice: once on the server and once on the client during hydration. This causes hydration issues, increases time to interactivity, and causes unpredictable behavior. useFetch and useAsyncData prevent this by ensuring that if an API call is made on the server, the data is forwarded to the client in the payload to avoid refetching during hydration.

The payload object and Nuxt DevTools

The payload is a JavaScript object accessible through useNuxtApp().payload. It is used on the client to avoid refetching the same data when code is executed in the browser during hydration. You can inspect payload data in the Nuxt DevTools Payload tab.

Suspense and async data fetching

Nuxt uses Vue's Suspense component under the hood to prevent navigation before every async data is available to the view. Data fetching composables help leverage this feature. You can add the NuxtLoadingIndicator component to add a progress bar between page navigations.

Await behavior in useFetch and useAsyncData

When awaiting useFetch or useAsyncData, execution pauses until data is ready, blocking navigation on client-side until data resolves. The await does not change server-rendered HTML—Nuxt waits for requests to resolve during server rendering regardless. Without await, execution continues immediately while the request runs in background, and data starts as its default value. With await, any code after the call can rely on data being populated. On client-side navigation, await blocks navigation while without await navigation happens immediately and you must handle loading and error states via returned status and error refs.

Lazy option vs not awaiting

Not awaiting has a similar user-visible effect to the lazy option (navigation not blocked, you handle loading state), but they are not identical. Lazy is an explicit flag that defers the request until component mount, whereas not awaiting starts the request during setup. Prefer lazy (or useLazyFetch/useLazyAsyncData) when you want non-blocking behavior, since it makes intent explicit.

Combining await with lazy option behavior

Await and lazy are independent. If you await a lazy call (for example await useLazyFetch(...) or await useFetch(..., { lazy: true })), it still blocks server rendering, but on client-side navigation the await resolves immediately without waiting for the request. Data will still be at its default value right after await, and you must handle loading state via status. If you want navigation to wait for data, drop the lazy option rather than relying on await.

$fetch does not provide network call de-duplication or navigation prevention

Using only $fetch will not provide network calls de-duplication and navigation prevention. It is recommended to use $fetch for client-side interactions (event-based) or combined with useAsyncData when fetching initial component data.

Headers and cookies with useFetch on server

When calling useFetch on the server with a relative URL, Nuxt will use useRequestFetch to proxy client headers and cookies to the API, with the exception of headers not meant to be forwarded like 'host'.

Headers to NOT proxy to external API

Be very careful before proxying headers to external API and only include headers you need. Common headers NOT to be proxied: host, accept, content-length, content-md5, content-type, x-forwarded-host, x-forwarded-port, x-forwarded-proto, cf-connecting-ip, cf-ray.

useFetch is a wrapper around useAsyncData and $fetch

useFetch(url) is nearly equivalent to useAsyncData(url, () => event.$fetch(url)). It is developer experience sugar for the most common use case.

useAsyncData with first argument as unique key

The first argument of useAsyncData is a unique key used to cache the response of the second argument (the querying function). This key can be ignored by directly passing the querying function, in which case the key will be auto-generated. Since auto-generated key only takes into account the location where useAsyncData is invoked, it is recommended to always create your own key to avoid unwanted behavior.

Return values from useFetch and useAsyncData

useFetch and useAsyncData return: data (result of async function), refresh/execute (function to refresh data), clear (function to set data to undefined or default value, error to undefined, status to idle, and cancel pending requests), error (error object if fetching failed), status (string: 'idle', 'pending', 'success', or 'error'). data, error and status are Vue refs accessible with .value in script setup.

Lazy option for data fetching

By default, data fetching composables wait for resolution before navigating to a new page using Vue's Suspense. The lazy option can be set to true to ignore this feature on client-side navigation, requiring manual handling of loading state using the status value. Alternatively, use useLazyFetch or useLazyAsyncData as convenient methods.

Client-only fetching with server: false option

Set the server option to false to only perform the data fetching call on the client-side. On initial load, data will not be fetched before hydration completes so you must handle a pending state. On subsequent client-side navigation, the data will be awaited before loading the page. Combined with lazy option, this is useful for data not needed on first render.

Minimize payload size with pick option

The pick option helps minimize payload size stored in HTML by only selecting fields to be returned from composables. The pick and transform options don't prevent unwanted data from being fetched initially, but they prevent unwanted data from being added to the payload transferred from server to client.

Transform function to alter query results

The transform function allows you to alter the result of a query. For example, you can use transform to map over results and select specific fields instead of using the pick option.

Caching with keys in useFetch and useAsyncData

useFetch and useAsyncData use keys to prevent refetching the same data. useFetch generates a key from URL, fetch options, and location in source code. Two useFetch calls with same URL in different components have different keys and perform their own request. To share data between components, provide same explicit key. useAsyncData uses first argument as key if string. useNuxtData can retrieve cached data by key.

Shared state and option consistency requirement

When multiple components use same key with useAsyncData or useFetch, they share same data, error and status refs. The following options MUST be consistent: handler function, deep option, transform function, pick array, getCachedData function, default value. Options that can safely differ: server, lazy, immediate, dedupe, watch.

Reactive keys in useAsyncData

You can use computed refs, plain refs or getter functions as keys in useAsyncData, allowing dynamic data fetching that automatically updates when dependencies change. When a reactive key changes, the data will be automatically refetched and old data cleaned up if no other components use it.

Watching reactive values with watch option

Use the watch option to re-run your fetching function each time other reactive values change. You can pass one or multiple watchable elements. Watching a reactive value won't change the URL fetched—the URL is constructed at the moment the function is invoked. Use computed URL instead if you need to change URL based on reactive values.

Opting out of automatic watching with watch: false

When reactive fetch options are provided, they are automatically watched and trigger refetches. You can opt-out by specifying watch: false to disable automatic watching of reactive options.

Computed URL with reactive values

You can attach fetch parameters as reactive values and Nuxt will automatically use them and re-fetch each time they change. For complex URL construction, use a callback as a computed getter that returns the URL string. Every time a dependency changes, data will be fetched using the newly constructed URL.

Immediate: false option for deferred fetching

The immediate: false option prevents useFetch from starting to fetch data the moment it is invoked. This is useful to wait for user interaction. With immediate: false, you need both status to handle fetch lifecycle and execute to start the data fetch.

Status values in data fetching

The status variable returned from data fetching composables can have values: 'idle' when fetch hasn't started, 'pending' when fetch started but not yet completed, 'error' when fetch fails, 'success' when fetch completed successfully.

useAsyncData should not be used for side effects

useAsyncData is for fetching and caching data, not triggering side effects like calling Pinia actions, as this can cause unintended behavior such as repeated executions with nullish values. If you need to trigger side effects, use the callOnce utility instead.

Server cookies not included in $fetch by default

Normally during server-side-rendering, $fetch would not include user's browser cookies or pass on cookies from fetch response due to security considerations. However, when calling useFetch with relative URL on server, Nuxt uses useRequestFetch to proxy headers and cookies.

Passing cookies from server-side API calls back to client

To proxy cookies from an internal request back to the client on SSR response, you need to handle this yourself. You can use $fetch.raw to get response headers, extract Set-Cookie headers, and append them to the outgoing response via event.res.headers.append('set-cookie', cookie).

Options API support with defineNuxtComponent

Nuxt provides asyncData fetching within Options API by wrapping component definition in defineNuxtComponent. Use the fetchKey option to provide a unique key, and define async asyncData() method to return data.

Data serialization from server to client with devalue

When using useAsyncData and useLazyAsyncData to transfer data from server to client, the payload is serialized with devalue library. This allows transferring not just basic JSON but also regular expressions, Dates, Map, Set, ref, reactive, shallowRef, shallowReactive, NuxtError and more. You can define custom serializer/deserializer for unsupported types in useNuxtApp docs.

Data serialization from API routes uses JSON.stringify

When fetching data from the server directory, response is serialized using JSON.stringify which is limited to JavaScript primitive types. Nuxt does its best to convert return type of $fetch and useFetch to match actual value, but custom serialization is possible using toJSON method.

Custom toJSON function for API route serialization

Define a toJSON function on returned object from API route to customize serialization behavior. If toJSON method is defined, Nuxt will respect the return type and will not try to convert types.

Example: useFetch basic usage

const { data: count } = await useFetch('/api/count') In template: <p>Page visits: {{ count }}</p> This shows basic useFetch usage to fetch data and display it.

Example: $fetch for POST request

async function addTodo () { const todo = await $fetch('/api/todos', { method: 'POST', body: { // My todo data }, }) } This shows $fetch used for event-based POST request.

Example: useAsyncData with explicit key

const { data, error } = await useAsyncData('users', () => myGetFunction('users')) Or without explicit key (auto-generated): const { data, error } = await useAsyncData(() => myGetFunction('users')) This shows useAsyncData with and without explicit key.

Example: useAsyncData with dynamic key for route params

const { id } = useRoute().params const { data, error } = await useAsyncData(`user:${id}`, () => { return myGetFunction('users', { id }) }) This shows using dynamic key with route parameters.

Example: Multiple parallel requests with Promise.all

const { data: discounts, status } = await useAsyncData('cart-discount', async (_nuxtApp, { signal }) => { const [coupons, offers] = await Promise.all([ $fetch('/cart/coupons', { signal }), $fetch('/cart/offers', { signal }), ]) return { coupons, offers } }) // discounts.value.coupons // discounts.value.offers This shows wrapping multiple parallel requests.

Example: lazy option for non-blocking data fetch

const { status, data: posts } = useFetch('/api/posts', { lazy: true, }) <template> <div v-if="status === 'pending'"> Loading ... </div> <div v-else> <div v-for="post in posts"> <!-- do something --> </div> </div> </template> This shows using lazy option to handle loading state manually.

Example: useLazyFetch convenience method

const { status, data: posts } = useLazyFetch('/api/posts') This is equivalent to useFetch with { lazy: true } option.

Example: Client-only fetching with server: false

/* This call is performed before hydration */ const articles = await useFetch('/api/article') /* This call will only be performed on the client */ const { status, data: comments } = useFetch('/api/comments', { lazy: true, server: false, }) This shows difference between server-side and client-only fetching.

Example: pick option to minimize payload

const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'], }) <template> <h1>{{ mountain.title }}</h1> <p>{{ mountain.description }}</p> </template> This shows using pick to only select needed fields.

Example: transform option to map results

const { data: mountains } = await useFetch('/api/mountains', { transform: (mountains) => { return mountains.map(mountain => ({ title: mountain.title, description: mountain.description })) }, }) This shows using transform to alter query results.

Example: refresh and execute functions

<script setup lang="ts"> const { data, error, execute, refresh } = await useFetch('/api/users') </script> <template> <div> <p>{{ data }}</p> <button @click="() => refresh()"> Refresh data </button> </div> </template> This shows how to manually refresh data.

Example: clear function to reset data

<script setup lang="ts"> const { data, clear } = await useFetch('/api/users') const route = useRoute() watch(() => route.path, (path) => { if (path === '/') { clear() } }) </script> This shows using clear function to reset data on route change.

Example: watch option to refetch on value change

const id = ref(1) const { data, error, refresh } = await useFetch('/api/users', { /* Changing the id will trigger a refetch */ watch: [id], }) This shows using watch to refetch when reactive value changes.

Example: Computed URL with reactive query parameters

const id = ref(null) const { data, status } = useLazyFetch('/api/user', { query: { user_id: id, }, }) When id changes, the data will be refetched automatically.

Example: Computed URL with callback getter

const id = ref(null) const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, { immediate: false, }) <template> <div> <input v-model="id" type="number" :disabled="status === 'pending'" > <div v-if="status === 'idle'"> Type a user ID </div> <div v-else-if="status === 'pending'"> Loading ... </div> <div v-else> {{ data }} </div> </div> </template> This shows using callback as computed URL with immediate: false.

Example: Options API with defineNuxtComponent

export default defineNuxtComponent({ fetchKey: 'hello', async asyncData () { return { hello: await $fetch('/api/hello'), } }, }) This shows data fetching in Options API using defineNuxtComponent.

Example: Custom toJSON serializer for API route

// server/api/bar.ts export default defineEventHandler(() => { const data = { createdAt: new Date(), toJSON () { return { createdAt: { year: this.createdAt.getFullYear(), month: this.createdAt.getMonth(), day: this.createdAt.getDate(), }, } }, } return data }) // app/app.vue const { data } = await useFetch('/api/bar') // Type of data is inferred as { createdAt: { year: number, month: number, day: number } } This shows custom serialization with toJSON method.

Example: Using superjson as alternative serializer

// server/api/superjson.ts import superjson from 'superjson' export default defineEventHandler(() => { const data = { createdAt: new Date(), toJSON () { return this }, } return superjson.stringify(data) as unknown as typeof data }) // app/app.vue import superjson from 'superjson' const { data } = await useFetch('/api/superjson', { transform: (value) => { return superjson.parse(value as unknown as string) }, }) This shows using superjson for advanced serialization.

Example: Consuming Server-Sent Events (SSE) via POST

const response = await $fetch<ReadableStream>('/chats/ask-ai', { method: 'POST', body: { query: 'Hello AI, how are you?', }, responseType: 'stream', }) const reader = response.pipeThrough(new TextDecoderStream()).getReader() while (true) { const { value, done } = await reader.read() if (done) { break } console.log('Received:', value) } This shows handling SSE via POST request with streaming response.

Example: Parallel requests with Promise.all

const { data } = await useAsyncData((_nuxtApp, { signal }) => { return Promise.all([ $fetch('/api/comments/', { signal }), $fetch('/api/author/12', { signal }), ]) }) const comments = computed(() => data.value?.[0]) const author = computed(() => data.value?.[1]) This shows making parallel requests and accessing individual results.

Hydration issues from double fetching

Using $fetch in Vue component setup can cause data to be fetched on server and again on client, leading to hydration mismatch errors and unpredictable component behavior as server-rendered HTML may not match client-rendered output.

Data not fetched before hydration with server: false

If you set server: false on useFetch, the data will not be fetched until hydration completes. This means even if you await useFetch on client-side, data will remain undefined within script setup.

Example: immediate: false to wait for user interaction

const { data, error, execute, status } = await useLazyFetch('/api/comments', { immediate: false, }) <template> <div v-if="status === 'idle'"> <button @click="execute"> Get data </button> </div> <div v-else-if="status === 'pending'"> Loading comments... </div> <div v-else> {{ data }} </div> </template> This shows using immediate: false to wait for button click.

Example: useRequestHeaders to access cookies

<script setup lang="ts"> const headers = useRequestHeaders(['cookie']) async function getCurrentUser () { return await $fetch('/api/me', { headers }) } </script> This shows accessing and sending cookies to API from server-side request.

Example: Pass cookies from server API call back to client

// app/composables/fetch.ts import type { H3Event } from 'h3' export const fetchWithCookie = async (event: H3Event, url: string) => { const res = await $fetch.raw(url) const cookies = res.headers.getSetCookie() for (const cookie of cookies) { event.res.headers.append('set-cookie', cookie) } return res._data } // In component: const event = useRequestEvent() const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie')) This shows handling cookie passthrough from internal API to client.

Isomorphic fetch in Nuxt 3

Nuxt 3 provides a globally available fetch method with the same API as the Fetch API and $fetch method (using unjs/ofetch). It smartly handles making direct API calls on the server or client-side calls to your API when running on the client. It comes with convenience features including automatically parsing responses and stringifying data.

Give your agent this brain