Remix / React Router routes
The server half for Remix and React Router apps: a splat resource route that proxies component requests to the Handset API. Your key stays server-side; your session decides tenant access.
Installation
npx shadcn@latest add @handset/remix-routesLands at app/routes/api.handset.$.ts, which serves /api/handset/* — the base path the components use by default. Built on the Web Request/Response API only, so the same file runs unchanged on Remix v2 and React Router 7.
Using React Router 7 with explicit route config? Register the splat in app/routes.ts:
route("api/handset/*", "routes/api.handset.$.ts")The auth boundary
One function is yours to implement. Everything the components can see flows through it:
async function resolveTenantId(request: Request): Promise<string | null> {
const session = await getSession(request.headers.get("Cookie"));
if (!session.has("userId")) throw new Response(null, { status: 401 });
return session.get("handsetTenantId"); // "tnt_…"
}The resolved tenant is injected into every read — anything the browser claims is ignored. Return null for single-tenant accounts.
The allowlist
Identical to the Next.js proxy: only the endpoints the components need are forwarded, everything else 404s. Buying numbers, porting, compliance — none of that is reachable from the browser. Extend the list deliberately if you build more.
Environment
HANDSET_API_KEY=sk_test_… # required; sk_live_… in production
HANDSET_API_URL= # optional override, defaults to https://api.handset.dev/v1