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-routes

Lands 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