Manage Apple Reminders via the `remindctl` CLI on macOS (list, add, edit, complete, delete)....
npx skills add modh-labs/ai-software-os --skill "data-fetching"
Install specific skill from multi-skill repository
# Description
Implement data fetching following your project's Server Component and Suspense streaming patterns. Use when fetching data in pages, implementing caching, parallel queries, or discussing data flow. Enforces repositories, Server Components, and cache invalidation.
# SKILL.md
name: data-fetching
description: Implement data fetching following your project's Server Component and Suspense streaming patterns. Use when fetching data in pages, implementing caching, parallel queries, or discussing data flow. Enforces repositories, Server Components, and cache invalidation.
allowed-tools: Read, Grep, Glob
Data Fetching Skill
When This Skill Activates
This skill automatically activates when you:
- Fetch data in Server Components
- Implement caching strategies
- Discuss data flow patterns
- Work with repositories in pages
Core Principle
All data fetching happens in Server Components via repositories.
Page (Server Component)
β calls
Repository (Data Access)
β queries
Supabase (via RLS)
β returns
Typed Data
β passes to
Client Components (display only)
Pattern 1: Basic Page Fetch
// app/(protected)/calls/page.tsx
import { createClient } from '@/app/_shared/lib/supabase/server';
import { callsRepository } from '@/app/_shared/repositories/calls.repository';
import { CallsTable } from './components/CallsTable';
export default async function CallsPage() {
const supabase = await createClient();
const calls = await callsRepository.list(supabase);
return <CallsTable calls={calls} />;
}
Pattern 2: Parallel Fetching
// β
CORRECT - Parallel fetches with Promise.all
export default async function DashboardPage() {
const supabase = await createClient();
const [calls, leads, analytics] = await Promise.all([
callsRepository.list(supabase),
leadsRepository.list(supabase),
analyticsRepository.getDashboard(supabase),
]);
return (
<Dashboard calls={calls} leads={leads} analytics={analytics} />
);
}
// β WRONG - Sequential fetches (slower)
const calls = await callsRepository.list(supabase);
const leads = await leadsRepository.list(supabase);
const analytics = await analyticsRepository.getDashboard(supabase);
Pattern 3: Streaming with Suspense
// page.tsx - Parent doesn't wait for children
export default function CallsPage() {
return (
<div>
<h1>Calls</h1>
<Suspense fallback={<CallsTableSkeleton />}>
<CallsTableSection />
</Suspense>
<Suspense fallback={<StatsSkeleton />}>
<StatsSection />
</Suspense>
</div>
);
}
// Async Server Component - fetches independently
async function CallsTableSection() {
const supabase = await createClient();
const calls = await callsRepository.list(supabase);
return <CallsTable calls={calls} />;
}
Pattern 4: Cache Invalidation
After mutations, ALWAYS invalidate cache:
// actions.ts
'use server'
export async function createCall(data: CreateCallInput) {
const supabase = await createClient();
const result = await callsRepository.create(supabase, data);
// β
ALWAYS invalidate after mutation
revalidatePath('/calls');
revalidatePath('/dashboard');
return { success: true, data: result };
}
Pattern 5: Error Handling
// β
CORRECT - Graceful degradation
export default async function DashboardPage() {
const supabase = await createClient();
const results = await Promise.allSettled([
callsRepository.list(supabase),
leadsRepository.list(supabase),
]);
const calls = results[0].status === 'fulfilled' ? results[0].value : [];
const leads = results[1].status === 'fulfilled' ? results[1].value : [];
return <Dashboard calls={calls} leads={leads} />;
}
Data Flow Rules
DO
- β Fetch in Server Components (pages, layouts)
- β Use repositories for ALL database access
- β
Use
Promise.all()for parallel fetches - β Use Suspense for streaming
- β
Call
revalidatePath()after mutations - β
Handle errors with
Promise.allSettled()
DON'T
- β Fetch in Client Components
- β Use
supabase.from()directly in pages - β Use React Query/SWR for Supabase data
- β Create API routes for internal data
- β Skip cache invalidation after mutations
When to Use What
| Scenario | Pattern |
|---|---|
| Simple page load | Basic fetch in page |
| Multiple independent queries | Promise.all() |
| Heavy page with sections | Suspense streaming |
| Form submission | Server Action + revalidatePath() |
| Real-time data | Supabase Realtime (rare) |
| External API | React Query (only for Nylas, etc.) |
Anti-Patterns
// β WRONG - Fetching in Client Component
'use client'
function CallsList() {
const [calls, setCalls] = useState([]);
useEffect(() => {
fetchCalls().then(setCalls); // Don't do this!
}, []);
}
// β WRONG - Direct Supabase in page
export default async function Page() {
const supabase = await createClient();
const { data } = await supabase.from('calls').select('*'); // Use repository!
}
// β WRONG - API route for internal data
// app/api/calls/route.ts - Don't create this!
Reference
For complete documentation: @docs/patterns/data-fetching.md
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.