convex-logto

Multiple environments

Dev / staging / prod. Every Logto value lives on the Convex deployment, so the frontend varies by one URL.

Create one Logto app per environment (dev / staging / prod), so tokens can't cross environments, then set each Convex deployment's env once:

# dev deployment
npx convex env set LOGTO_ENDPOINT      https://your-logto.example.com
npx convex env set LOGTO_APP_ID        <dev-app-id>
npx convex env set LOGTO_CLIENT_SECRET <dev-app-secret>
# production deployment
npx convex env set --prod LOGTO_ENDPOINT      https://your-logto.example.com
npx convex env set --prod LOGTO_APP_ID        <prod-app-id>
npx convex env set --prod LOGTO_CLIENT_SECRET <prod-app-secret>
# staging: target that deployment the same way

In session mode the frontend carries no Logto values, so the only thing that varies per environment is the Convex deployment it points at, which you already set via VITE_CONVEX_URL (or NEXT_PUBLIC_CONVEX_URL, EXPO_PUBLIC_CONVEX_URL). Vite, Next.js, and Expo embed that value at build time, so each environment still gets its own build; it differs from the others by that one URL.

Each environment's Logto app needs its own Redirect URI and Post sign-out redirect URI, and, if you use them, its own webhook and back-channel logout URI. Those point at the deployment's .convex.site URL, which also differs per deployment.

Bridge mode

Bridge mode needs no LOGTO_CLIENT_SECRET, but its provider takes the endpoint and app id from the frontend. With static config set the two public values in each frontend environment (VITE_LOGTO_ENDPOINT and VITE_LOGTO_APP_ID, or the NEXT_PUBLIC_* / EXPO_PUBLIC_* equivalents).

To keep those two values out of the bundle as well, export logtoConfigQuery() from convex/logto.ts and pass configQuery={api.logto.config} to the provider. The frontend then pulls them from whichever deployment VITE_CONVEX_URL selects, at the cost of one query round trip before the Logto client can mount.

On this page