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 wayIn 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.