convex-logto

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.

src/main.tsx
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.

src/router.tsx
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.

src/main.tsx
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.

On this page