import { useAgent } from "agents/react"; import { nanoid } from "nanoid"; import { useState, useEffect } from "react"; import { Button, Input, Surface, Text, Radio } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, ConnectionStatus, CodeExplanation, type CodeSection } from "../../components"; import { useLogs, useUserId } from "../../hooks"; import type { RoutingAgent, RoutingAgentState } from "./routing-agent"; const codeSections: CodeSection[] = [ { title: "Route by agent name", description: "The name prop on useAgent determines which Durable Object instance you connect to. Same name = same agent. Use user IDs, session IDs, or fixed strings to control isolation.", code: `// Per-user: each user gets their own agent const agent = useAgent({ agent: "routing-agent", name: \`user-\${userId}\`, }); // Shared: everyone on the same instance const agent = useAgent({ agent: "routing-agent", name: "shared", }); // Per-session: each browser tab is isolated const agent = useAgent({ agent: "routing-agent", name: \`session-\${sessionId}\`, });` }, { title: "Custom routing with basePath", description: "For server-controlled routing, use basePath instead of name. Your Worker's fetch handler resolves the URL to the right Durable Object using getAgentByName().", code: `// Client: connect via a custom URL path const agent = useAgent({ agent: "routing-agent", basePath: \`custom-routing/\${userId}\`, }); // Server: resolve the path in your Worker export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname.startsWith("/custom-routing/")) { const name = url.pathname.split("/")[2]; const agent = getAgentByName(env.RoutingAgent, name); return agent.fetch(request); } } }` } ]; type RoutingStrategy = "per-user" | "shared" | "per-session" | "custom-path"; function getSessionId(): string { if (typeof window === "undefined") return "session-1"; let sessionId = sessionStorage.getItem("playground-session-id"); if (!sessionId) { sessionId = `session-${nanoid(6)}`; sessionStorage.setItem("playground-session-id", sessionId); } return sessionId; } export function RoutingDemo() { const initialUserId = useUserId(); const { logs, addLog, clearLogs } = useLogs(); const [userId, setUserId] = useState(initialUserId); const [strategy, setStrategy] = useState("per-user"); const [connectionCount, setConnectionCount] = useState(0); const [agentInstanceName, setAgentInstanceName] = useState(""); const getAgentName = () => { switch (strategy) { case "per-user": return `routing-${userId}`; case "shared": return "routing-shared"; case "per-session": return `routing-${getSessionId()}`; case "custom-path": return `routing-${userId}`; default: return "routing-demo"; } }; const currentAgentName = getAgentName(); const isCustomPath = strategy === "custom-path"; const agent = useAgent({ agent: "routing-agent", name: isCustomPath ? undefined : currentAgentName, basePath: isCustomPath ? `custom-routing/${currentAgentName}` : undefined, onOpen: () => { if (!isCustomPath) { addLog("info", "connected", `Agent: ${currentAgentName}`); setAgentInstanceName(currentAgentName); } else { addLog( "info", "connected", `Custom path: /custom-routing/${currentAgentName}` ); } }, onIdentity: (name, agentType) => { addLog("info", "identity", `Server resolved: ${agentType}/${name}`); setAgentInstanceName(name); }, onClose: () => addLog("info", "disconnected"), onError: () => addLog("error", "error", "Connection error"), onStateUpdate: (newState) => { setConnectionCount(newState.counter); addLog("in", "state_update", { counter: newState.counter }); } }); useEffect(() => { localStorage.setItem("playground-user-id", userId); }, [userId]); const openNewTab = () => { window.open(window.location.href, "_blank"); }; const strategies: { id: RoutingStrategy; label: string; description: string; }[] = [ { id: "per-user", label: "Per-User", description: "Each user ID gets their own agent instance" }, { id: "shared", label: "Shared", description: "All users share a single agent instance" }, { id: "per-session", label: "Per-Session", description: "Each browser session gets its own agent" }, { id: "custom-path", label: "Custom Path (basePath)", description: "Server-side routing via a custom URL path using getAgentByName" } ]; return ( The{" "} name{" "} prop on{" "} useAgent {" "} determines which Durable Object instance you connect to — same name means same agent. Use user IDs for per-user isolation, a fixed string for a shared singleton, or session IDs for per-tab agents. For server-controlled routing, use{" "} basePath {" "} instead. Switch strategies below and open multiple tabs to see the difference. } statusIndicator={ } >
{/* Controls */}
{/* Connection Status */}
Agent Instance: {agentInstanceName || "connecting..."}
Counter: {connectionCount}
{/* User Identity */}
Your Identity
) => setUserId(e.target.value) } className="w-full" placeholder="Enter a user ID" />
Session ID (auto-generated per tab) {getSessionId()}
{/* Strategy Selector */}
Routing Strategy
{ setStrategy(value as RoutingStrategy); addLog("out", "strategy_change", value); }} > {strategies.map((s) => ( ))}
{/* Multi-Tab Testing */}
Try It Out

Open multiple tabs to see how different strategies affect which clients end up on the same agent instance.

{/* Explanation */}
How It Works

Per-User: Agent name ={" "} routing-{userId}
Same user across tabs/devices shares an agent

Shared: Agent name = routing-shared
Everyone connects to the same agent

Per-Session:{" "} Agent name ={" "} routing-{getSessionId()}
Each browser tab gets its own agent

Custom Path:{" "} basePath ={" "} /custom-routing/routing-{userId}
Server handles routing via{" "} getAgentByName — client uses{" "} basePath instead of{" "} agent/ name

{/* Logs */}
); }