When to Use
- Building Next.js pages, layouts, or components
- Creating FastAPI API endpoints
- Designing database schemas with Drizzle ORM
- Implementing data fetching with TanStack Query
- Building full-stack features end-to-end
Instructions
Step 1: Next.js 15 / React 19 Rules
// ✅ Prefer Server Components — no 'use client' unless interactive
// app/dashboard/page.tsx
import { DataTable } from "@/components/data-table";
import { getAnalysisResults } from "@/lib/db/queries";
export default async function DashboardPage({ searchParams, }: { searchParams: Promise<{ page?: string }>; }) { const { page } = await searchParams; // Always await in App Router const results = await getAnalysisResults({ page: Number(page ?? 1) });
return ; }
```typescript
// ✅ Client components: only when you need interactivity
"use client";
import { useActionState } from "react"; // NOT useFormState (deprecated)
export function UploadForm() {
const [state, formAction, isPending] = useActionState(uploadAction, null);
return <form action={formAction}>...</form>;
}
Step 2: FastAPI Endpoint Pattern
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel, Field
from typing import Annotated
app = FastAPI()
class AnalysisRequest(BaseModel):
file_id: str = Field(..., description="Uploaded file identifier")
method: Literal["anova", "ttest", "regression"] = "anova"
class AnalysisResponse(BaseModel):
result_id: str
summary: str
p_value: float
effect_size: float
@app.post("/api/v1/analyze", response_model=AnalysisResponse, status_code=202)
async def run_analysis(
request: AnalysisRequest,
db: Annotated[AsyncSession, Depends(get_db)],
current_user: Annotated[User, Depends(get_current_user)],
) -> AnalysisResponse:
"""Trigger statistical analysis on an uploaded file."""
file = await db.get(UploadedFile, request.file_id)
if not file or file.owner_id != current_user.id:
raise HTTPException(404, "File not found")
result = await analysis_service.run(file, request.method)
return AnalysisResponse(**result.model_dump())
Step 3: Database Schema (Drizzle ORM)
// db/schema/analysis.ts
import { pgTable, text, timestamp, real, uuid } from "drizzle-orm/pg-core";
export const analysisResults = pgTable("analysis_results", {
id: uuid("id").primaryKey().defaultRandom(),
fileId: uuid("file_id").notNull().references(() => uploadedFiles.id),
userId: uuid("user_id").notNull().references(() => users.id),
method: text("method").notNull(),
pValue: real("p_value"),
effectSize: real("effect_size"),
summary: text("summary"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
Step 4: Data Fetching (TanStack Query 5.95+)
// hooks/use-analysis.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
export function useAnalysisResult(resultId: string) {
return useQuery({
queryKey: ["analysis", resultId],
queryFn: () => api.analysis.get(resultId),
enabled: !!resultId,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useRunAnalysis() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (req: AnalysisRequest) => api.analysis.post(req),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["analysis"] });
},
});
}
Step 5: Error Handling Pattern
// lib/api-client.ts — centralized error handling
export class ApiError extends Error {
constructor(
public readonly status: number,
message: string,
public readonly code?: string,
) {
super(message);
}
}
async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(url, options);
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new ApiError(res.status, body.message ?? "Request failed", body.code);
}
return res.json();
}
Step 6: Component Structure
// components/data-table.tsx — standard component structure
import type { AnalysisResult } from "@/types";
// 1. Types at top
type Props = {
data: AnalysisResult[];
onRowClick?: (row: AnalysisResult) => void;
};
// 2. Main component
export function DataTable({ data, onRowClick }: Props) {
// 3. Hooks
// 4. Derived state (useMemo)
// 5. Callbacks (useCallback)
// 6. Early returns for loading/empty states
if (!data.length) return <EmptyState />;
// 7. Render
}
// 8. Sub-components below
function EmptyState() { ... }
Output Format
For each feature, provide:
- Database schema (if new tables needed)
- API endpoint (FastAPI)
- React component or hook (Next.js)
- Types/interfaces
Activate Specialist
@.cursor/.agents/.agents/fullstack-developer.md