Skip to content

Wrap your app in <BridgeProvider> once at the root, then wrap any route element that needs auth in the ProtectedRoute component. ProtectedRoute handles the login redirect automatically.

// src/main.tsx
import { BridgeProvider } from '@nebulr-group/bridge-react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <BridgeProvider config={{ loginRoute: '/auth/login' }}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </BridgeProvider>
);

How it works:

PieceWhat it does
<ProtectedRoute>While auth state is loading it renders a loading placeholder. Once resolved, an unauthenticated user is sent to your loginRoute (or Bridge’s hosted login page when it is unset); an authenticated user sees the route’s content.
Public routesAny route you don’t wrap in <ProtectedRoute> is public.
Router adapterBridge-driven redirects (the OAuth callback, the billing paywall) navigate through a RouterAdapter, so they use your router’s client-side navigation instead of a full reload.

Framework note: bridge-react has no declarative route rule engine (a RouteGuardConfig with rules, defaultAccess, per-rule featureFlag, and billing gates). Auth protection is per-route with <ProtectedRoute>. Flag gating is wired manually (below).

Someone who follows a link into a protected page — an emailed document link, a bookmark, a shared URL — lands on that page after signing in, not on your default route. This is on by default; you do not configure anything to get it.

How the target travels depends on which login you use:

ModeMechanismYour job
Hosted (no loginRoute)Held in sessionStorage across the OAuth round-tripNothing. <CallbackHandler> restores it
SDK (you set loginRoute)?redirectUri= on your own login routeRead it after login — below

Your login page owns the post-login navigation, so it has to read the target. Use readReturnTo — it validates the value for you:

import { LoginForm, readReturnTo } from '@nebulr-group/bridge-react';
import { useNavigate } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();

  return (
    <LoginForm
      onLogin={() => {
        // Falls back to your own default when there is no target, or when the
        // one supplied is not safe to navigate to.
        navigate(readReturnTo(window.location.search) ?? '/dashboard');
      }}
    />
  );
}

Your loginRoute is excluded automatically, so a bounce through the login page never comes back pointing at itself. Exclude the rest of your auth flow too:

<BridgeProvider
  config={{
    appId: '…',
    loginRoute: '/auth/login',
    returnTo: { exclude: [new RegExp('^/auth($|/)')] },
  }}
>

To send every login to the same place regardless of where the visitor was heading:

returnTo: { enabled: false }

A path that fails validation is treated the same way: your login page gets null and falls back to its own default.

Bridge-driven redirects (the route guard, the OAuth callback, the billing paywall) use your router’s client-side navigation once Bridge knows your router. Without that, Bridge falls back to window.location, which still works but forces a full reload.

The router adapter entries do it for you: <BridgeAuthRoutes> and <BridgeBillingRoutes> from @nebulr-group/bridge-react/react-router or @nebulr-group/bridge-react/tanstack-router register the router while mounted. An app that renders neither calls useBridgeRouter() from the same entry once, inside the router:

import { useBridgeRouter } from '@nebulr-group/bridge-react/react-router';

function App() {
  useBridgeRouter();
  return <Routes>{/* ... */}</Routes>;
}

The router packages are optional peers (react-router 6.4 to 7, @tanstack/react-router 1): only the entry you import needs its router installed.

For any other router, the main entry still ships createReactRouterAdapter, createTanStackRouterAdapter and createWouterAdapter, or implement RouterAdapter yourself (navigate, replace, getCurrentPath), and call setRouterAdapter() with it.

Check the flag inside the route and redirect manually:

import { useFlag } from '@nebulr-group/bridge-react/flags';
import type { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';

function BetaRoute({ children }: { children: ReactNode }) {
  const { value } = useFlag('beta_feature', false);
  return value ? <>{children}</> : <Navigate to="/" replace />;
}

Wrap the flag-gated route in this component the same way you’d wrap it in <ProtectedRoute> (or nest both, if the route needs auth and a flag).

<BridgeProvider> redirects an authenticated workspace that hasn’t selected a plan to the paywall on load: /subscription/plan by default, served by <BridgeBillingRoutes> mounted at /subscription/* — only when the app has plans. Set billing.paywallRoute in the config reference to move it (e.g. /welcome rendering <BridgePaywallPage />), or false to turn it off. A paywall route of your own must stay reachable for a signed-in, plan-less user.