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,
useFetch
anduseAsyncData
run 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
useLazyAsyncData
or setlazy: true
. $fetch
alone 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
useFetch
for HTTP endpoints you need to render the page - Use
useAsyncData
for custom logic or when combining multiple sources - Use
useLazyAsyncData
when you don’t want to block navigation - Always handle loading and error states in your components
- Use the
refresh
function 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.