feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
357
skills/nuxt/references/best-practices-data-fetching.md
Normal file
357
skills/nuxt/references/best-practices-data-fetching.md
Normal file
@@ -0,0 +1,357 @@
|
||||
---
|
||||
name: data-fetching-best-practices
|
||||
description: Patterns and best practices for efficient data fetching in Nuxt
|
||||
---
|
||||
|
||||
# Data Fetching Best Practices
|
||||
|
||||
Effective data fetching patterns for SSR-friendly, performant Nuxt applications.
|
||||
|
||||
## Choose the Right Tool
|
||||
|
||||
| Scenario | Use |
|
||||
|----------|-----|
|
||||
| Component initial data | `useFetch` or `useAsyncData` |
|
||||
| User interactions (clicks, forms) | `$fetch` |
|
||||
| Third-party SDK/API | `useAsyncData` with custom function |
|
||||
| Multiple parallel requests | `useAsyncData` with `Promise.all` |
|
||||
|
||||
## Await vs Non-Await Usage
|
||||
|
||||
The `await` keyword controls whether data fetching **blocks navigation**:
|
||||
|
||||
### With `await` - Blocking Navigation
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Navigation waits until data is fetched (uses Vue Suspense)
|
||||
const { data } = await useFetch('/api/posts')
|
||||
// data.value is available immediately after this line
|
||||
</script>
|
||||
```
|
||||
|
||||
- **Server**: Fetches data and includes it in the payload
|
||||
- **Client hydration**: Uses payload data, no re-fetch
|
||||
- **Client navigation**: Blocks until data is ready
|
||||
|
||||
### Without `await` - Non-Blocking (Lazy)
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Navigation proceeds immediately, data fetches in background
|
||||
const { data, status } = useFetch('/api/posts', { lazy: true })
|
||||
// data.value may be undefined initially - check status!
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="status === 'pending'">Loading...</div>
|
||||
<div v-else>{{ data }}</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Equivalent to using `useLazyFetch`:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data, status } = useLazyFetch('/api/posts')
|
||||
</script>
|
||||
```
|
||||
|
||||
### When to Use Each
|
||||
|
||||
| Pattern | Use Case |
|
||||
|---------|----------|
|
||||
| `await useFetch()` | Critical data needed for SEO/initial render |
|
||||
| `useFetch({ lazy: true })` | Non-critical data, better perceived performance |
|
||||
| `await useLazyFetch()` | Same as lazy, await only ensures initialization |
|
||||
|
||||
## Avoid Double Fetching
|
||||
|
||||
### ❌ Wrong: Using $fetch Alone in Setup
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// This fetches TWICE: once on server, once on client
|
||||
const data = await $fetch('/api/posts')
|
||||
</script>
|
||||
```
|
||||
|
||||
### ✅ Correct: Use useFetch
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Fetches on server, hydrates on client (no double fetch)
|
||||
const { data } = await useFetch('/api/posts')
|
||||
</script>
|
||||
```
|
||||
|
||||
## Use Explicit Cache Keys
|
||||
|
||||
### ❌ Avoid: Auto-generated Keys
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Key is auto-generated from file/line - can cause issues
|
||||
const { data } = await useAsyncData(() => fetchPosts())
|
||||
</script>
|
||||
```
|
||||
|
||||
### ✅ Better: Explicit Keys
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Explicit key for predictable caching
|
||||
const { data } = await useAsyncData(
|
||||
'posts',
|
||||
() => fetchPosts(),
|
||||
)
|
||||
|
||||
// Dynamic keys for parameterized data
|
||||
const route = useRoute()
|
||||
const { data: post } = await useAsyncData(
|
||||
`post-${route.params.id}`,
|
||||
() => fetchPost(route.params.id),
|
||||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
## Handle Loading States Properly
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data, status, error } = await useFetch('/api/posts')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="status === 'pending'">
|
||||
<SkeletonLoader />
|
||||
</div>
|
||||
<div v-else-if="error">
|
||||
<ErrorMessage :error="error" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<PostList :posts="data" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Use Lazy Fetching for Non-critical Data
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const id = useRoute().params.id
|
||||
|
||||
// Critical data - blocks navigation
|
||||
const { data: post } = await useFetch(`/api/posts/${id}`)
|
||||
|
||||
// Non-critical data - doesn't block navigation
|
||||
const { data: comments, status } = useFetch(`/api/posts/${id}/comments`, {
|
||||
lazy: true,
|
||||
})
|
||||
|
||||
// Or use useLazyFetch
|
||||
const { data: related } = useLazyFetch(`/api/posts/${id}/related`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<h1>{{ post?.title }}</h1>
|
||||
<p>{{ post?.content }}</p>
|
||||
</article>
|
||||
|
||||
<section v-if="status === 'pending'">Loading comments...</section>
|
||||
<CommentList v-else :comments="comments" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Minimize Payload Size
|
||||
|
||||
### Use `pick` for Simple Filtering
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data } = await useFetch('/api/users', {
|
||||
// Only include these fields in payload
|
||||
pick: ['id', 'name', 'avatar'],
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### Use `transform` for Complex Transformations
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data } = await useFetch('/api/posts', {
|
||||
transform: (posts) => {
|
||||
return posts.map(post => ({
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
excerpt: post.content.slice(0, 100),
|
||||
date: new Date(post.createdAt).toLocaleDateString(),
|
||||
}))
|
||||
},
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Parallel Fetching
|
||||
|
||||
### Fetch Independent Data with useAsyncData
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data } = await useAsyncData(
|
||||
'dashboard',
|
||||
async (_nuxtApp, { signal }) => {
|
||||
const [user, posts, stats] = await Promise.all([
|
||||
$fetch('/api/user', { signal }),
|
||||
$fetch('/api/posts', { signal }),
|
||||
$fetch('/api/stats', { signal }),
|
||||
])
|
||||
return { user, posts, stats }
|
||||
},
|
||||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
### Multiple useFetch Calls
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// These run in parallel automatically
|
||||
const [{ data: user }, { data: posts }] = await Promise.all([
|
||||
useFetch('/api/user'),
|
||||
useFetch('/api/posts'),
|
||||
])
|
||||
</script>
|
||||
```
|
||||
|
||||
## Efficient Refresh Patterns
|
||||
|
||||
### Watch Reactive Dependencies
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const page = ref(1)
|
||||
const category = ref('all')
|
||||
|
||||
const { data } = await useFetch('/api/posts', {
|
||||
query: { page, category },
|
||||
// Auto-refresh when these change
|
||||
watch: [page, category],
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### Manual Refresh
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data, refresh, status } = await useFetch('/api/posts')
|
||||
|
||||
async function refreshPosts() {
|
||||
await refresh()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Conditional Fetching
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const userId = ref<string | null>(null)
|
||||
|
||||
const { data, execute } = useFetch(() => `/api/users/${userId.value}`, {
|
||||
immediate: false, // Don't fetch until userId is set
|
||||
})
|
||||
|
||||
// Later, when userId is available
|
||||
function loadUser(id: string) {
|
||||
userId.value = id
|
||||
execute()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Server-only Fetching
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Only fetch on server, skip on client navigation
|
||||
const { data } = await useFetch('/api/static-content', {
|
||||
server: true,
|
||||
lazy: true,
|
||||
getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key],
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { data, error, refresh } = await useFetch('/api/posts')
|
||||
|
||||
// Watch for errors if need event-like handling
|
||||
watch(error, (err) => {
|
||||
if (err) {
|
||||
console.error('Fetch failed:', err)
|
||||
// Show toast, redirect, etc.
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="error">
|
||||
<p>Failed to load: {{ error.message }}</p>
|
||||
<button @click="refresh()">Retry</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Shared Data Across Components
|
||||
|
||||
```vue
|
||||
<!-- ComponentA.vue -->
|
||||
<script setup lang="ts">
|
||||
const { data } = await useFetch('/api/user', { key: 'current-user' })
|
||||
</script>
|
||||
|
||||
<!-- ComponentB.vue -->
|
||||
<script setup lang="ts">
|
||||
// Access cached data without refetching
|
||||
const { data: user } = useNuxtData('current-user')
|
||||
|
||||
// Or refresh it
|
||||
const { refresh } = await useFetch('/api/user', { key: 'current-user' })
|
||||
</script>
|
||||
```
|
||||
|
||||
## Avoid useAsyncData for Side Effects
|
||||
|
||||
### ❌ Wrong: Side Effects in useAsyncData
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Don't trigger Pinia actions or side effects
|
||||
await useAsyncData(() => store.fetchUser()) // Can cause issues
|
||||
</script>
|
||||
```
|
||||
|
||||
### ✅ Correct: Use callOnce for Side Effects
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
await callOnce(async () => {
|
||||
await store.fetchUser()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
<!--
|
||||
Source references:
|
||||
- https://nuxt.com/docs/getting-started/data-fetching
|
||||
- https://nuxt.com/docs/api/composables/use-fetch
|
||||
- https://nuxt.com/docs/api/composables/use-async-data
|
||||
- https://nuxt.com/docs/api/composables/use-lazy-fetch
|
||||
-->
|
||||
Reference in New Issue
Block a user