Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Nuxt 3.x reference covering SSR, file-based routing, auto-imports, server routes, and Nitro deployment.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/best-practices-data-fetching.md
1---2name: data-fetching-best-practices3description: Patterns and best practices for efficient data fetching in Nuxt4---56# Data Fetching Best Practices78Effective data fetching patterns for SSR-friendly, performant Nuxt applications.910## Choose the Right Tool1112| Scenario | Use |13|----------|-----|14| Component initial data | `useFetch` or `useAsyncData` |15| User interactions (clicks, forms) | `$fetch` |16| Third-party SDK/API | `useAsyncData` with custom function |17| Multiple parallel requests | `useAsyncData` with `Promise.all` |1819## Await vs Non-Await Usage2021The `await` keyword controls whether data fetching **blocks navigation**:2223### With `await` - Blocking Navigation2425```vue26<script setup lang="ts">27// Navigation waits until data is fetched (uses Vue Suspense)28const { data } = await useFetch('/api/posts')29// data.value is available immediately after this line30</script>31```3233- **Server**: Fetches data and includes it in the payload34- **Client hydration**: Uses payload data, no re-fetch35- **Client navigation**: Blocks until data is ready3637### Without `await` - Non-Blocking (Lazy)3839```vue40<script setup lang="ts">41// Navigation proceeds immediately, data fetches in background42const { data, status } = useFetch('/api/posts', { lazy: true })43// data.value may be undefined initially - check status!44</script>4546<template>47<div v-if="status === 'pending'">Loading...</div>48<div v-else>{{ data }}</div>49</template>50```5152Equivalent to using `useLazyFetch`:5354```vue55<script setup lang="ts">56const { data, status } = useLazyFetch('/api/posts')57</script>58```5960### When to Use Each6162| Pattern | Use Case |63|---------|----------|64| `await useFetch()` | Critical data needed for SEO/initial render |65| `useFetch({ lazy: true })` | Non-critical data, better perceived performance |66| `await useLazyFetch()` | Same as lazy, await only ensures initialization |6768## Avoid Double Fetching6970### ❌ Wrong: Using $fetch Alone in Setup7172```vue73<script setup lang="ts">74// This fetches TWICE: once on server, once on client75const data = await $fetch('/api/posts')76</script>77```7879### ✅ Correct: Use useFetch8081```vue82<script setup lang="ts">83// Fetches on server, hydrates on client (no double fetch)84const { data } = await useFetch('/api/posts')85</script>86```8788## Use Explicit Cache Keys8990### ❌ Avoid: Auto-generated Keys9192```vue93<script setup lang="ts">94// Key is auto-generated from file/line - can cause issues95const { data } = await useAsyncData(() => fetchPosts())96</script>97```9899### ✅ Better: Explicit Keys100101```vue102<script setup lang="ts">103// Explicit key for predictable caching104const { data } = await useAsyncData(105'posts',106() => fetchPosts(),107)108109// Dynamic keys for parameterized data110const route = useRoute()111const { data: post } = await useAsyncData(112`post-${route.params.id}`,113() => fetchPost(route.params.id),114)115</script>116```117118## Handle Loading States Properly119120```vue121<script setup lang="ts">122const { data, status, error } = await useFetch('/api/posts')123</script>124125<template>126<div v-if="status === 'pending'">127<SkeletonLoader />128</div>129<div v-else-if="error">130<ErrorMessage :error="error" />131</div>132<div v-else>133<PostList :posts="data" />134</div>135</template>136```137138## Use Lazy Fetching for Non-critical Data139140```vue141<script setup lang="ts">142const id = useRoute().params.id143144// Critical data - blocks navigation145const { data: post } = await useFetch(`/api/posts/${id}`)146147// Non-critical data - doesn't block navigation148const { data: comments, status } = useFetch(`/api/posts/${id}/comments`, {149lazy: true,150})151152// Or use useLazyFetch153const { data: related } = useLazyFetch(`/api/posts/${id}/related`)154</script>155156<template>157<article>158<h1>{{ post?.title }}</h1>159<p>{{ post?.content }}</p>160</article>161162<section v-if="status === 'pending'">Loading comments...</section>163<CommentList v-else :comments="comments" />164</template>165```166167## Minimize Payload Size168169### Use `pick` for Simple Filtering170171```vue172<script setup lang="ts">173const { data } = await useFetch('/api/users', {174// Only include these fields in payload175pick: ['id', 'name', 'avatar'],176})177</script>178```179180### Use `transform` for Complex Transformations181182```vue183<script setup lang="ts">184const { data } = await useFetch('/api/posts', {185transform: (posts) => {186return posts.map(post => ({187id: post.id,188title: post.title,189excerpt: post.content.slice(0, 100),190date: new Date(post.createdAt).toLocaleDateString(),191}))192},193})194</script>195```196197## Parallel Fetching198199### Fetch Independent Data with useAsyncData200201```vue202<script setup lang="ts">203const { data } = await useAsyncData(204'dashboard',205async (_nuxtApp, { signal }) => {206const [user, posts, stats] = await Promise.all([207$fetch('/api/user', { signal }),208$fetch('/api/posts', { signal }),209$fetch('/api/stats', { signal }),210])211return { user, posts, stats }212},213)214</script>215```216217### Multiple useFetch Calls218219```vue220<script setup lang="ts">221// These run in parallel automatically222const [{ data: user }, { data: posts }] = await Promise.all([223useFetch('/api/user'),224useFetch('/api/posts'),225])226</script>227```228229## Efficient Refresh Patterns230231### Watch Reactive Dependencies232233```vue234<script setup lang="ts">235const page = ref(1)236const category = ref('all')237238const { data } = await useFetch('/api/posts', {239query: { page, category },240// Auto-refresh when these change241watch: [page, category],242})243</script>244```245246### Manual Refresh247248```vue249<script setup lang="ts">250const { data, refresh, status } = await useFetch('/api/posts')251252async function refreshPosts() {253await refresh()254}255</script>256```257258### Conditional Fetching259260```vue261<script setup lang="ts">262const userId = ref<string | null>(null)263264const { data, execute } = useFetch(() => `/api/users/${userId.value}`, {265immediate: false, // Don't fetch until userId is set266})267268// Later, when userId is available269function loadUser(id: string) {270userId.value = id271execute()272}273</script>274```275276## Server-only Fetching277278```vue279<script setup lang="ts">280// Only fetch on server, skip on client navigation281const { data } = await useFetch('/api/static-content', {282server: true,283lazy: true,284getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key],285})286</script>287```288289## Error Handling290291```vue292<script setup lang="ts">293const { data, error, refresh } = await useFetch('/api/posts')294295// Watch for errors if need event-like handling296watch(error, (err) => {297if (err) {298console.error('Fetch failed:', err)299// Show toast, redirect, etc.300}301}, { immediate: true })302</script>303304<template>305<div v-if="error">306<p>Failed to load: {{ error.message }}</p>307<button @click="refresh()">Retry</button>308</div>309</template>310```311312## Shared Data Across Components313314```vue315<!-- ComponentA.vue -->316<script setup lang="ts">317const { data } = await useFetch('/api/user', { key: 'current-user' })318</script>319320<!-- ComponentB.vue -->321<script setup lang="ts">322// Access cached data without refetching323const { data: user } = useNuxtData('current-user')324325// Or refresh it326const { refresh } = await useFetch('/api/user', { key: 'current-user' })327</script>328```329330## Avoid useAsyncData for Side Effects331332### ❌ Wrong: Side Effects in useAsyncData333334```vue335<script setup lang="ts">336// Don't trigger Pinia actions or side effects337await useAsyncData(() => store.fetchUser()) // Can cause issues338</script>339```340341### ✅ Correct: Use callOnce for Side Effects342343```vue344<script setup lang="ts">345await callOnce(async () => {346await store.fetchUser()347})348</script>349```350351<!--352Source references:353- https://nuxt.com/docs/getting-started/data-fetching354- https://nuxt.com/docs/api/composables/use-fetch355- https://nuxt.com/docs/api/composables/use-async-data356- https://nuxt.com/docs/api/composables/use-lazy-fetch357-->358