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
f1f4c3e2
Server Timestamp
2026-03-09T09:04:43.673Z
Server PID
318

How It Works

1. Create and register context (context/server-session.ts)

import { createContext } from "react-router";
import { registerContext } from "@udibo/juniper";

export interface ServerSession {
  sessionId: string;
  serverTimestamp: string;
  serverPid: number;
}

export const serverSessionContext = createContext<ServerSession>();

export function createServerSession(): ServerSession {
  return {
    sessionId: crypto.randomUUID().slice(0, 8),
    serverTimestamp: new Date().toISOString(),
    serverPid: Deno.pid,
  };
}

// Register serialization for this context
registerContext<ServerSession>({
  name: "serverSession",
  context: serverSessionContext,
  serialize: (session) => session,
  deserialize: (data) => data as ServerSession,
});

2. Set context in server middleware (routes/main.ts)

import { Hono } from "hono";
import type { AppEnv } from "@udibo/juniper/server";
import {
  createServerSession,
  serverSessionContext,
} from "@/context/server-session.ts";

const app = new Hono<AppEnv>();

// Set server session in context for all routes
app.use(async (c, next) => {
  const context = c.get("context");
  context.set(serverSessionContext, createServerSession());
  await next();
});

export default app;

3. 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>;
}

4. 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.