Data Fetching in Nuxt 3: A Comprehensive Guide
Updated on: 2025-08-20
Nuxt 3 introduces powerful data fetching composables that streamline building Vue applications with great performance and SEO. This guide explores useAsyncData, useLazyAsyncData, and useFetch, showing when and how to use each with practical examples.
Table of Contents
- Understanding Nuxt 3 Data Fetching
- useFetch
- useAsyncData
- useLazyAsyncData
- Comparison and Best Practices
- FAQs
- Conclusion
Understanding Nuxt 3 Data Fetching
Nuxt 3's data fetching system is designed to be intuitive and powerful. These composables integrate with SSR to render data on the server and hydrate on the client.
- By default,
useFetchanduseAsyncDatarun during SSR and block navigation on client transitions until their handlers resolve. - If you don’t want to block navigation (for non‑critical or below‑the‑fold data), use
useLazyAsyncDataor setlazy: true. $fetchalone is great for event‑based actions (like submitting a form), but for initial page data you want the SSR hydration and deduplication that the data composables provide.
useFetch
What is useFetch?
useFetch is ideal when you call HTTP endpoints (REST/GraphQL). It wraps useAsyncData and $fetch, giving you a simple API with SSR hydration and request deduplication.
Basic Usage
const { data, pending, error, refresh } = await useFetch("/api/users");
Key Features
- Auto-imports and type hints for your API routes
- Automatic request deduplication
- Payload extraction for better performance
- TypeScript support with automated type inference
Advanced Usage
const { data, pending, error, refresh } = await useFetch("/api/comments", {
method: "post",
body: { comment: "Great post!" },
query: { postId: 1 },
onRequest({ request, options }) {
// Set the request headers
options.headers = options.headers || {};
options.headers.authorization = "...";
},
onRequestError({ request, options, error }) {
// Handle the request errors
},
onResponse({ request, response, options }) {
// Process the response data
},
onResponseError({ request, response, options }) {
// Handle the response errors
},
});
Common Use Cases for useFetch
- API Data Retrieval
const { data: posts } = await useFetch("/api/posts");
- Form Submissions
const { data, error } = await useFetch("/api/submit", {
method: "POST",
body: { name, email, message },
});
- Pagination
const page = ref(1);
const { data: paginatedPosts } = await useFetch(
() => `/api/posts?page=${page.value}&limit=10`,
{ watch: [page] }
);
- Real-time Data Updates (Polling)
const { data: liveData, refresh } = await useFetch("/api/live-data");
const timer = setInterval(() => {
refresh();
}, 5000);
onScopeDispose(() => {
clearInterval(timer);
});
useAsyncData
What is useAsyncData?
useAsyncData is a flexible composable that lets you use any asynchronous function for data fetching. It’s great for combining multiple sources, custom clients/SDKs, or shaping payloads before they reach your component.
Basic Usage
const { data, pending, error, refresh } = await useAsyncData("users", () =>
$fetch("/api/users")
);
Key Features
- More control over the fetching process
- Ability to use custom asynchronous functions
- Options for lazy loading and server-only execution
useAsyncData blocks navigation until its async handler is resolved. Use lazy: true or useLazyAsyncData if you don’t want to block.
Common Use Cases for useAsyncData
- Complex Data Fetching
const { data: processedData } = await useAsyncData(
"processedData",
async () => {
const [users, posts] = await Promise.all([
$fetch("/api/users"),
$fetch("/api/posts"),
]);
return processData(users, posts);
}
);
- Defaults and Transform (replace custom cache keys)
const { data: items } = await useAsyncData(
"items", // key used for caching/invalidation
() => $fetch("/api/items"),
{
default: () => [],
transform: (list) => list.map((i) => ({ id: i.id, name: i.name })),
}
);
- Server-Only Data Fetching
const { data: sensitiveData } = await useAsyncData(
"sensitiveData",
() => $fetch("/api/sensitive-data"),
{ server: true }
);
- Combining Multiple Data Sources
const { data: combinedData } = await useAsyncData("combinedData", async () => {
const apiData = await $fetch("/api/data");
const localData = await loadLocalData();
return mergeData(apiData, localData);
});
Note on keys: The first argument (e.g., "items") is the unique key used for caching and invalidation. There is no separate cacheKey option.
useLazyAsyncData
What is useLazyAsyncData?
useLazyAsyncData is similar to useAsyncData, but it doesn't block navigation. It’s useful when you want to load data after the initial page load.
Basic Usage
const {
pending,
data: users,
refresh,
} = useLazyAsyncData("users", () => $fetch("/api/users"));
useLazyAsyncData triggers navigation before the handler is resolved by setting the lazy option to true.
Common Use Cases for useLazyAsyncData
- Loading Non-Critical Data
const { data: additionalInfo } = useLazyAsyncData("additionalInfo", () =>
$fetch("/api/additional-info")
);
- Infinite Scrolling
const page = ref(1);
const { data: posts, refresh } = useLazyAsyncData(
"posts",
() => $fetch(`/api/posts?page=${page.value}`),
{ watch: [page] }
);
const loadMore = () => {
page.value++;
refresh();
};
- User-Triggered Data Loading
const { data: userDetails, execute } = useLazyAsyncData(
"userDetails",
() => $fetch(`/api/user/${userId.value}`),
{ immediate: false }
);
const loadUserDetails = () => {
execute();
};
- Conditional Data Fetching
const shouldFetch = ref(false);
const { data: conditionalData } = useLazyAsyncData(
"conditionalData",
() => (shouldFetch.value ? $fetch("/api/data") : Promise.resolve(null)),
{ watch: [shouldFetch] }
);
Comparison and Best Practices
| Composable | Best Use Case | SSR Behavior | Client-Side Behavior |
|---|---|---|---|
| useFetch | HTTP endpoints (REST/GraphQL) | Fetches on server, caches result | Uses cached data, refetches if needed |
| useAsyncData | Complex/custom async logic | Blocks navigation until resolved | Same as server-side |
| useLazyAsyncData | Non-critical or user-triggered | Doesn't block navigation | Fetches data after component mount |
Best Practices
- Use
useFetchfor HTTP endpoints you need to render the page - Use
useAsyncDatafor custom logic or when combining multiple sources - Use
useLazyAsyncDatawhen you don’t want to block navigation - Always handle loading and error states in your components
- Use the
refreshfunction orrefreshNuxtData(key)after mutations
FAQs
- Q: When should I use useFetch over useAsyncData?
A: Use useFetch when you’re calling an HTTP endpoint and want a simple API with SSR hydration and deduplication. Use useAsyncData for arbitrary async work (multiple sources, SDKs, or payload shaping).
- Q: How does Nuxt 3 handle SSR with these composables?
A: Nuxt executes these composables on the server during SSR, then serializes and transfers the result to the client, avoiding unnecessary refetching.
- Q: Can I use these composables with external APIs?
A: Yes, all these composables work with both internal and external API endpoints.
- Q: How do I handle authentication with these composables?
A: You can use the onRequest option to add authentication headers to your requests; on the server, forward cookies/headers using useRequestHeaders.
- Q: Is it possible to prefetch data for multiple pages?
A: Yes, you can use these composables in your layout files or implement custom prefetching logic using Nuxt's routing system.
Conclusion
Mastering Nuxt 3's data fetching composables helps you build performant, SEO-friendly applications. By understanding the strengths of useFetch, useAsyncData, and useLazyAsyncData, you can choose the right tool for each scenario in your Nuxt 3 projects.