TanStack Router
Mount the provider in a TanStack Router SPA. Soft navigation, a callback route, and beforeLoad guards, in both modes.
Finish Quick start steps 1 to 3 (the backend is framework-agnostic), then wire the provider and callback the TanStack Router way.
Session mode
Pass navigate so the post-sign-in landing is a soft router navigation rather
than a full reload. replace: true keeps the code-bearing callback URL out of
history.
import { ConvexReactClient } from "convex/react";
import { ConvexLogtoSessionProvider } from "convex-logto/react-session";
import { RouterProvider } from "@tanstack/react-router";
import { api } from "../convex/_generated/api";
import { router } from "./router";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL, {
initialAuthTokenReuse: true,
});
root.render(
<ConvexLogtoSessionProvider
client={convex}
sessionApi={api.auth}
navigate={(to) => void router.navigate({ to, replace: true })}
>
<RouterProvider router={router} />
</ConvexLogtoSessionProvider>,
);Callback route
Keep a /callback route. The provider completes the exchange and then calls
your navigate, but the router still renders that path first; without a route
for it the user sees a 404 for the length of one round trip.
const callbackRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/callback",
component: () => <p>Finishing sign in…</p>,
});Route guards
For redirect-based protection you need auth outside render, in a route's
beforeLoad. Lift useLogtoAuth() into the router context and guard there.
The full pattern, with the isLoading-first check that avoids bouncing a
just-returned user, is in Auth in your app → Route
guards.
A router-level guard is not an authorization boundary. It decides what to
render, and the Convex function still has to check the identity itself.
assertSubjectHasActiveSession(ctx, components.logto) makes a function fail the
moment a session is revoked, rather than when the ID token expires.
Full app: examples/vite-react-session
(the session-mode wiring is router-independent).
Bridge mode
Bridge mode changes the provider and adds the two public Logto values to the frontend env. The router wiring is unchanged.
import { ConvexLogtoProvider } from "convex-logto/react";
root.render(
<ConvexLogtoProvider
client={convex}
config={{
endpoint: import.meta.env.VITE_LOGTO_ENDPOINT,
appId: import.meta.env.VITE_LOGTO_APP_ID,
}}
navigate={(to) => void router.navigate({ to, replace: true })}
>
<RouterProvider router={router} />
</ConvexLogtoProvider>,
);Keep the same /callback route. Route guards work as above; useLogtoAuth()
from convex-logto/react has the same isAuthenticated / isLoading pair,
and both come from Convex, so they flip only once Convex has accepted the
token.
Full app: examples/tanstack-router-spa,
which also carries the webhook-synced users table and
RBAC.