SPAs & cross-domain linking
Use this guide when your product spans multiple domains (for example a marketing site and a signed-in app) or when you use client-side routing (React Router, Next.js, and similar).
How linking works
Configure allowed domains for your environment in the console. After the SDK loads, outbound links to another allowed domain receive a short-lived _rcquery parameter with the visitor's viewerId and sessionId. On arrival, the destination SDK reads and strips _rc, persists the ids, and calls renderContext so experiments and flags stay consistent.
The link param expires after 120 seconds. Exposure and context tokens are not passed in links — the destination re-mints them via its own renderContext call.
The one rule for single-page apps
Load rendercontext.min.js before your app bundle runs. If your framework boots client-side routing first, an auth redirect can drop _rc from the URL before the SDK reads it — and the destination site will mint a new visitor id.
Put the install snippet in index.html before your application module — not from useEffect.
React hooks
After loading the browser SDK with the Vite plugin, a Next.js script, or the plain snippet, install the React helpers to read values inside components.
pnpm add @rendercontext/sdk react
import { useIsEnabled } from "@rendercontext/sdk/react";
function Checkout() {
const enabled = useIsEnabled("new_checkout", false);
return enabled ? <NewCheckout /> : <LegacyCheckout />;
}The hooks wait for the browser SDK and return a safe default while it loads. Use useFlag for string values and useVariant for experiments.
Vite / React (SPA)
Use the @rendercontext/sdk/vite plugin to inject the install snippet before your <script type="module"> entry. Deferred scripts run in document order — RenderContext loads first.
pnpm add -D @rendercontext/sdk viteimport { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { renderContext } from "@rendercontext/sdk/vite";
export default defineConfig({
plugins: [
renderContext({ projectKey: process.env.VITE_ANALYTICS_PROJECT_KEY }),
react(),
],
});Or put the script in index.html manually before your module entry:
<script
defer
src="https://cdn.rendercontext.com/rendercontext.min.js"
data-project="pk_live_YOUR_KEY"
></script>
<script type="module" src="/src/main.tsx"></script>Next.js
Use @rendercontext/sdk/next in your root layout or pages/_document.tsx. Do not inject the script from useEffect — client routing can run first.
pnpm add @rendercontext/sdk next// app/layout.tsx
import { RenderContextScript } from "@rendercontext/sdk/next";
export default function RootLayout({ children }) {
return (
<html>
<body>
<RenderContextScript
projectKey={process.env.NEXT_PUBLIC_ANALYTICS_PROJECT_KEY}
/>
{children}
</body>
</html>
);
}Pages Router: put the component in <Head> inside pages/_document.tsx. Cross-domain linking passes visitor ids in the URL query string — if the router runs before the SDK loads, those params can be overwritten.
Auth and viewer context
Cross-domain linking only transfers viewer and session ids. If you use signed-in flag targeting, wire viewer context after auth resolves — not while auth is still loading.
- Loading: do nothing
- Signed in:
await renderContext.setViewerContext(...) - Signed out (confirmed):
clearViewerContext()only if you previously set viewer context
See Signing tokens for attribute-based targeting.
Anti-patterns
- Injecting
rendercontext.min.jsfromuseEffectwhen the URL might change before the SDK runs - Client-side redirects that drop the query string before the SDK loads (unless the script already ran)
- Calling
clearViewerContext()on every unsigned-in page load - Calling
clearViewerContext()while auth is still restoring session state
Verify it works
- Open the marketing site, wait for renderContext in the Network tab.
- Click a link to the app domain. The arrival URL should briefly include
_rc(it may be stripped immediately after load). - On the app domain, confirm renderContext sends the same
viewerIdas the marketing site (check the request body orlocalStoragekeysrc_hashed_viewer_id/rc_session_id).