FrontendData Fetching
Overview
Server components, server actions, and the API proxy pattern.
HyperSaaS uses Next.js server components as the primary data fetching layer. All data flows from the Django backend through authenticated fetch calls, validated with Zod schemas.
Data Flow
Django Backend ← JWT Auth → Next.js Server Component → Client Component (props)
│
▼
Zod schema validation
│
▼
TypeScript type inferenceFetching Patterns
1. Server Component (Primary)
Most pages are server components that fetch data at render time:
export default async function WorkspacePage({ params }) {
const { workspaceId } = await params;
const user = await getCurrentUserServer();
const workspace = await fetchWorkspace(workspaceId);
const sessions = await getWorkspaceChatSessions(workspaceId);
return <ChatHistory sessions={sessions} workspace={workspace} />;
}2. API Route Proxy (Client Components)
Client components that need to make requests (forms, real-time updates) call Next.js API routes, which proxy to the Django backend:
// Client component
const res = await fetch(`/api/workspaces/${workspaceId}/chats`, {
method: "POST",
body: JSON.stringify({ name: "New Chat", ai_model: "gpt-4o" }),
});3. Parallel Fetching
Server components use Promise.all for parallel data fetching:
const [user, workspaces, invitations] = await Promise.all([
getCurrentUserServer(),
getUserWorkspaces(),
getWorkspaceInvitations(workspaceId),
]);Caching Strategy
Most fetches use cache: "no-store" for real-time data:
const res = await fetch(url, {
headers: { Authorization: `JWT ${token}` },
cache: "no-store",
});Chat session pages also set revalidate = 0 to prevent stale data.
Error Handling
Server Components
const { data, error } = await fetchApiData(endpoint, schema);
if (error) {
return <ErrorMessage message={error} />;
}Client Components
try {
const res = await fetch(url, options);
if (!res.ok) {
const data = await res.json();
toast.error(data.detail || "Something went wrong");
return;
}
// success
} catch (err) {
toast.error("Network error");
}Generic Fetch Utilities
fetchApiData
Type-safe fetch with automatic Zod validation:
async function fetchApiData<T>(
endpoint: string,
schema: z.ZodSchema<T>
): Promise<{ data: T | null; error?: string }> {
const token = await getAccessToken();
const res = await fetch(`${BACKEND_URL}${endpoint}`, {
headers: { Authorization: `JWT ${token}` },
cache: "no-store",
});
if (!res.ok) return { data: null, error: "Failed to fetch" };
const json = await res.json();
const parsed = schema.safeParse(json);
if (!parsed.success) return { data: null, error: "Invalid response" };
return { data: parsed.data };
}authenticatedFetch
Lower-level fetch with JWT injection:
async function authenticatedFetch<T>(
url: string,
method: string,
token: string,
body?: any
): Promise<T> {
const res = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `JWT ${token}`,
},
body: body ? JSON.stringify(body) : undefined,
});
return res.json();
}