Client + Server Loader

Client Loader Calls Server Loader

When you have both a server loader and a client loader, the client loader can call serverLoader() to fetch server data and then enrich it with client-side information. This pattern combines the security of server-side data loading with client-specific enhancements.

Why Use This Pattern?

  • Security: Keep sensitive data fetching on the server
  • Client Context: Add browser-specific data (localStorage, navigator, etc.)
  • Caching: Layer client-side caching on top of server data
  • Transformation: Transform server data for client rendering

Server Data

Message
Secure data from server
Timestamp
2026-01-11T20:48:38.289Z
Secret
API Key Hash: 527kl5ym

Client Enrichment

Client Time
Browser
Combined

Client Loader Calling Server (client-calls-server.tsx)

import type { RouteLoaderArgs } from "@udibo/juniper";

// ServerData type is defined in the same .tsx file
export interface ServerData {
  serverMessage: string;
  serverTimestamp: string;
  secretData: string;
}

interface ClientEnrichedData extends ServerData {
  clientTimestamp: string;
  browserInfo: string;
  combinedMessage: string;
}

export async function loader({
  serverLoader,
}: RouteLoaderArgs): Promise<ClientEnrichedData> {
  const serverData = await serverLoader() as ServerData;

  return {
    ...serverData,
    clientTimestamp: new Date().toISOString(),
    browserInfo: navigator.userAgent.split(" ").slice(-1)[0],
    combinedMessage: `${serverData.serverMessage} + client enrichment`,
  };
}

Note: During SSR, the server loader runs directly. On client-side navigation, the client loader calls serverLoader() which fetches data from a server endpoint, then enriches it with client context.