---
name: data
description: "Use when implementing data fetching, API calls, server/client components, or SWR hooks"
---
Source Cursor rule: `.cursor/rules/data.mdc`.
Original Cursor alwaysApply: `false`.
# Data Fetching
## Core Pattern: Server → Client → SWR
### 1. Server Page Fetches Data
```tsx
// app/(app)/[orgId]/tasks/page.tsx
export default async function TasksPage({ params }: { params: Promise<{ orgId: string }> }) {
const { orgId } = await params; // From URL, NOT session
const tasks = await getTasks(orgId);
return ;
}
```
### 2. Client Component Receives Initial Data
```tsx
// components/TaskListClient.tsx
'use client';
export function TaskListClient({ organizationId, initialTasks }: Props) {
const { tasks, createTask, updateTask } = useTasks({
organizationId,
initialData: initialTasks,
});
// Initial render is instant - no loading state
}
```
### 3. SWR Hook with fallbackData
```tsx
// hooks/useTasks.ts
export function useTasks({ organizationId, initialData }: UseTasksOptions) {
const { data, mutate } = useSWR(
['/v1/tasks', organizationId], // Include orgId for cache isolation
async ([endpoint, orgId]) => {
const response = await apiClient.get(endpoint, orgId);
return response.data?.tasks ?? [];
},
{ fallbackData: initialData }
);
const createTask = async (input: CreateTaskInput) => {
await apiClient.post('/v1/tasks', input, organizationId);
mutate(); // Revalidate
};
const updateTask = async ({ taskId, input }: { taskId: string; input: UpdateTaskInput }) => {
await apiClient.put(`/v1/tasks/${taskId}`, input, organizationId);
mutate(); // Revalidate
};
return { tasks: data ?? [], createTask, updateTask, mutate };
}
```
## API Client
Use `apiClient` from `@/lib/api-client`:
```tsx
import { apiClient } from '@/lib/api-client';
await apiClient.get('/v1/endpoint', organizationId);
await apiClient.post('/v1/endpoint', body, organizationId);
await apiClient.put('/v1/endpoint', body, organizationId);
await apiClient.delete('/v1/endpoint', organizationId);
```
## Server vs Client Components
**Layouts = server.** Interactive logic in separate client components.
```tsx
// layout.tsx (server)
export default function Layout({ children }) {
return (
{/* Client component */}
{children}
);
}
// components/ClientTabs.tsx
'use client';
export function ClientTabs() {
const router = useRouter();
// Interactive logic here
}
```
## State Management
**No `nuqs`** - use React state or Next.js patterns:
```tsx
// ✅ React state for UI
const [isOpen, setIsOpen] = useState(false);
// ✅ Next.js for URL state
const router = useRouter();
const searchParams = useSearchParams();
// ❌ No nuqs
import { useQueryState } from 'nuqs';
```
## Rules
```tsx
// ✅ Always
const { orgId } = await params; // From URL params
const { data } = useSWR(key, f, { fallbackData }); // With initial data
await apiClient.get('/v1/endpoint', orgId); // Use apiClient
useSWR(['/v1/tasks', orgId], fetcher); // Include orgId in key
// ❌ Never
const orgId = session?.activeOrganizationId; // From session
const { data } = useSWR('/api/data'); // No initial data
await fetch('/api/endpoint'); // Direct fetch
```
## File Structure
```
app/(app)/[orgId]/tasks/
├── page.tsx # Server - fetches data
├── components/
│ └── TaskListClient.tsx # Client - receives initialData
├── hooks/
│ └── useTasks.ts # SWR hook with mutations
└── data/
└── queries.ts # Server-side queries
```