Context Serialization
Context Serialization
Context serialization allows server-side context values to be transferred to the client during hydration. This enables the client to access data that was set by server middleware without making additional requests.
When to Use Context Serialization
- Sharing user session data across server and client
- Passing configuration set during SSR to the client
- Hydrating client-side state managers (e.g., TanStack Query)
- Avoiding duplicate data fetching on initial page load
Server Session Data
This data was set by server middleware and serialized to the client:
- Session ID
- e64e8710
- Server Timestamp
- 2026-01-11T20:41:55.718Z
- Server PID
- 361
How It Works
1. Create shared context (context/server-session.ts)
import { createContext } from "react-router";
export interface ServerSession {
sessionId: string;
serverTimestamp: string;
serverPid: number;
}
export const serverSessionContext = createContext<ServerSession>();2. Deserialize context on client (routes/main.tsx)
import { RouterContextProvider } from "react-router";
import { serverSessionContext } from "@/context/server-session.ts";
import type { ServerSession } from "@/context/server-session.ts";
export interface SerializedContext {
serverSession?: ServerSession;
}
export function deserializeContext(
serializedContext?: SerializedContext,
): RouterContextProvider {
const context = new RouterContextProvider();
if (serializedContext?.serverSession) {
context.set(serverSessionContext, serializedContext.serverSession);
}
return context;
}3. Set context and serialize (routes/main.ts)
import type { RouterContextProvider } from "react-router";
import type { AppEnv } from "@udibo/juniper/server";
import { serverSessionContext } from "@/context/server-session.ts";
import type { ServerSession } from "@/context/server-session.ts";
import type { SerializedContext } from "./main.tsx";
const app = new Hono<AppEnv>();
// Set server session in context for all routes
app.use(async (c, next) => {
const context = c.get("context");
const serverSession: ServerSession = {
sessionId: crypto.randomUUID().slice(0, 8),
serverTimestamp: new Date().toISOString(),
serverPid: Deno.pid,
};
context.set(serverSessionContext, serverSession);
await next();
});
export function serializeContext(
context: RouterContextProvider,
): SerializedContext {
const serializedContext: SerializedContext = {};
try {
const serverSession = context.get(serverSessionContext);
serializedContext.serverSession = serverSession;
} catch {
// serverSession not set in context
}
return serializedContext;
}4. Access context in loaders (routes/my-route.tsx)
import type { AnyParams, RouteLoaderArgs, RouteProps } from "@udibo/juniper";
import { serverSessionContext } from "@/context/server-session.ts";
import type { ServerSession } from "@/context/server-session.ts";
// Loaders run on both server (SSR) and client (navigation)
export function loader({ context }: RouteLoaderArgs) {
return context.get(serverSessionContext);
}
export default function MyRoute({ loaderData }: RouteProps<AnyParams, ServerSession>) {
return <p>Session ID: {loaderData.sessionId}</p>;
}5. Browser-only code in loaders (optional)
import type { RouteLoaderArgs } from "@udibo/juniper";
import { isBrowser } from "@udibo/juniper/utils/env";
export function loader({ context }: RouteLoaderArgs) {
const data = context.get(myContext);
// Use isBrowser() for browser-only APIs
if (isBrowser()) {
// This only runs on client-side navigation, not during SSR
localStorage.setItem("lastVisited", new Date().toISOString());
}
return data;
}Note: The server session data shown above was set during SSR and transferred to the client via context serialization. The client can access this data immediately without making any additional requests.