Agent Skills
› trycompai/comp
› data
data
GitHub规范 Next.js 数据获取模式,定义服务端页面预取、客户端组件接收初始数据及 SWR Hook 缓存更新的核心流程。提供 API 客户端使用指南,明确服务器与客户端组件的职责划分,并规定状态管理需遵循 React 标准而非 nuqs。
Trigger Scenarios
实现数据获取逻辑
调用 API 接口
开发服务端或客户端组件
编写 SWR 数据钩子
Install
npx skills add trycompai/comp --skill data -g -y
SKILL.md
Frontmatter
{
"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
// 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 <TaskListClient organizationId={orgId} initialTasks={tasks} />;
}
2. Client Component Receives Initial Data
// 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
// 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:
import { apiClient } from '@/lib/api-client';
await apiClient.get<ResponseType>('/v1/endpoint', organizationId);
await apiClient.post<ResponseType>('/v1/endpoint', body, organizationId);
await apiClient.put<ResponseType>('/v1/endpoint', body, organizationId);
await apiClient.delete('/v1/endpoint', organizationId);
Server vs Client Components
Layouts = server. Interactive logic in separate client components.
// layout.tsx (server)
export default function Layout({ children }) {
return (
<PageLayout>
<PageHeader title="Title" />
<ClientTabs /> {/* Client component */}
{children}
</PageLayout>
);
}
// 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:
// ✅ 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
// ✅ 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
Version History
- 0ccfcc2 Current 2026-09-27 10:22


