From harness-claude
Guides optimal data fetching in Nuxt pages and components using useFetch, useAsyncData, and useLazyFetch for SSR/SSG without duplicate requests.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Fetch data and coordinate async state across server and client using Nuxt's built-in composables
Guides Nuxt 5 data management with useFetch, useAsyncData, useState, and Pinia for creating composables, fetching data, managing state, and debugging reactive/SSR issues.
Guides Nuxt 4 data fetching with useFetch/useAsyncData, state management via useState/Pinia, and custom composables for SSR-safe reactive patterns.
Provides Nuxt 4 patterns for hydration safety, performance via route rules and lazy loading, and SSR-safe data fetching with useFetch and useAsyncData. Use when debugging SSR mismatches, rendering strategies, or page data fetching.
Share bugs, ideas, or general feedback.
Fetch data and coordinate async state across server and client using Nuxt's built-in composables
useFetch, useAsyncData, and useLazyFetchuseNuxtApp) for plugins or runtime configuseFetch as the default for HTTP requests — it wraps useAsyncData + $fetch and handles SSR deduplication automatically:// pages/posts/[id].vue
const { data: post, pending, error } = await useFetch(`/api/posts/${route.params.id}`);
useAsyncData when the async operation is not a simple HTTP call (e.g., database query via a server composable, computed key based on reactive state):const { data: user } = await useAsyncData(
'user-profile', // unique cache key — required
() => fetchUserProfile(userId.value),
{ watch: [userId] } // re-fetch when userId changes
);
useLazyFetch / useLazyAsyncData to defer data loading — the page renders immediately and pending is true until data arrives:// Good for non-critical data below the fold
const { data: comments, pending } = useLazyFetch('/api/comments');
Always provide a unique string key to useAsyncData. Keys collide across components — use route params or namespacing to avoid cache pollution.
Use $fetch directly only in event handlers (button clicks, form submissions) — not at the top of setup(), which would bypass SSR deduplication:
// Good: event handler
async function submitForm() {
await $fetch('/api/contact', { method: 'POST', body: formData.value });
}
// Bad: top-level setup — use useFetch instead
const data = await $fetch('/api/posts'); // runs on both server AND client
transform option to avoid storing raw API shapes:const { data: posts } = await useFetch('/api/posts', {
transform: (raw) => raw.items.map((p) => ({ id: p.id, title: p.title })),
});
useNuxtApp():const { $toast, $i18n, ssrContext } = useNuxtApp();
Nuxt's data fetching composables solve a fundamental SSR problem: if you use plain fetch() or axios in setup(), the request fires on the server during SSR and again on the client during hydration — doubling your API calls. useFetch and useAsyncData solve this by serializing the server-fetched data into the HTML payload and rehydrating it on the client without a second network request.
Key differences:
| Composable | Use case | Blocks navigation |
|---|---|---|
useFetch | Simple HTTP calls | Yes (await) |
useAsyncData | Custom async logic, reactive keys | Yes (await) |
useLazyFetch | Non-blocking HTTP | No |
useLazyAsyncData | Non-blocking custom async | No |
$fetch | Event handlers only | N/A |
Cache key design:
useAsyncData keys are global within a Nuxt app instance. Two components using useAsyncData('posts', ...) will share data. This is often desired (shared cache) but causes bugs when the same key is used with different fetch functions. Pattern: <entity>-<id> or <page>-<section>.
Error handling:
const { data, error } = await useFetch('/api/posts');
if (error.value) {
throw createError({ statusCode: 500, message: 'Failed to load posts' });
}
Refreshing data:
const { data, refresh } = await useFetch('/api/posts');
// Later:
await refresh();
https://nuxt.com/docs/getting-started/data-fetching