Integration
React Router
React Router 7 and Remix, in framework and SPA mode
Applies to React Router 7 in framework mode and to Remix, which shares the same links / loader API.
npm install @superbridge/widgetThe stylesheet
Import both stylesheets as URLs and return them from the root route's links export, widget first.
// app/root.tsx
import type { LinksFunction } from "react-router";
import widgetStyles from "@superbridge/widget/styles.css?url";
import appStyles from "./app.css?url";
export const links: LinksFunction = () => [
// Widget first: its theme variables are global, and yours should win.
{ rel: "stylesheet", href: widgetStyles },
{ rel: "stylesheet", href: appStyles },
];The widget
Framework mode server-renders, and there's no built-in client-only boundary, so gate it yourself. This is the whole thing:
// app/components/client-only.tsx
import { useEffect, useState, type ReactNode } from "react";
export function ClientOnly({
children,
fallback = null,
}: {
children: () => ReactNode;
fallback?: ReactNode;
}) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return <>{mounted ? children() : fallback}</>;
}children is a function so the widget isn't even constructed during the server render. (If you already depend on remix-utils, its ClientOnly is the same component with the same signature.)
// app/routes/bridge.tsx
import { Widget } from "@superbridge/widget";
import { ClientOnly } from "../components/client-only";
export default function BridgePage() {
return (
<main className="mx-auto max-w-lg py-24">
<ClientOnly fallback={<div className="h-[560px] w-full" />}>
{() => <Widget localDevelopmentApiKey={import.meta.env.VITE_WIDGET_DEV_KEY} />}
</ClientOnly>
</main>
);
}SPA mode
With ssr: false in react-router.config.ts there is no server render, so drop the gate and render <Widget /> directly — the Vite page is the shape you want.
Environment variables
React Router 7 and Remix both build with Vite, so client-visible variables are VITE_-prefixed and inlined at build time. Gate the dev key on import.meta.env.DEV or define it only in .env.development.
Don't route the dev key through a loader instead — that publishes it on every request, in production too.
If your app owns the wallet stack, add resolve.dedupe: ["react", "react-dom", "wagmi", "viem", "@tanstack/react-query"] to your Vite config so the widget sees the provider you mounted rather than a second copy of wagmi. See Wallets.
Checklist
- Both stylesheets in the root
links, widget first - A client-only boundary whose
childrenis a function - Wallet providers inside the same boundary
- Dev key gated to development