Deployment
Deploy a Nuxt and Convex application in the right order, with working SSR, realtime, and authentication.
Deploy Convex and Nuxt as two services. Convex runs the database and your functions. Nuxt renders pages, serves the browser bundle, runs server routes, and runs the auth proxy.
For a step-by-step guide on one host, see Deploy to Vercel.
What you need
- a Convex production deployment;
- a Nuxt host that runs the Nitro server;
- one public HTTPS origin for the Nuxt application;
- browser access to the Convex HTTPS and WebSocket endpoints.
Authentication needs the /api/auth/* Nitro route, so a static host without
a server cannot run it. An application without convex.auth can use Nuxt's
static output, but test its SSR and client navigation first.
Select the production deployment
Every Convex command below uses pnpm exec better-convex convex. This wrapper
reads the target deployment only from .env.local in the current folder. It
ignores CONVEX_* variables from the shell and rejects target flags such as
--prod.
For production, run the commands from a separate checkout of your repository
with its dependencies installed. Its .env.local contains only the production
deploy key:
CONVEX_DEPLOY_KEY=prod:your-deployment|your-keyCreate the key in the Convex dashboard, under the production deployment's
settings. deploy refuses a development key and a plain CONVEX_DEPLOYMENT
name. Check the file before every production command. Never commit it.
Deployment order
Deploy the Convex schema, functions, HTTP routes, and auth component:
bash pnpm exec better-convex convex deploy- Set
SITE_URL,BETTER_AUTH_SECRETS, andBCN_AUTH_PROXY_IP_SECRETin Convex, as described in environment variables. Convex sets itsCONVEX_SITE_URLbuilt-in itself. Create the signing key once, before the first user signs in:
bash pnpm exec better-convex convex run auth:ensureSigningKey '{}'The action returns
{ created, kid }. Running it again returns the same key.- Build and deploy Nuxt with its environment variables. Before you announce
the release, open
https://app.example.com/api/auth/jwksand check that it lists the returnedkid. - For MCP, create the host OAuth clients with
auth.oauthOperator. See Delegated OAuth and MCP. - Test sign-in on the deployed origin, then open the application to users.
Retired signing keys stay published for 21 minutes after a rotation. Schedule
the internal auth:pruneSigningKeys mutation, for example as a daily Convex
cron, to delete keys after that time. It deletes at most batchSize keys per
call (1 to 256, default 64) and never the current key. Run it again while it
returns hasMore: true.
Production checklist
- Install the exact Better Auth and OAuth provider versions from the package manifest. See release compatibility.
- Set an exact HTTPS
SITE_URLwithout a path, and pass the same value asconvex.auth.origin. - Keep
BETTER_AUTH_SECRETSout of Nuxt. KeepBCN_AUTH_PROXY_IP_SECRETout of public config and logs. - Set
NUXT_PUBLIC_CONVEX_SITE_URLto the exact HTTP Actions origin. Do not try to override theCONVEX_SITE_URLbuilt-in in Convex. - Check that
/api/auth/*reaches the intended.convex.siteorigin. - Allow the exact Convex HTTPS and WebSocket origins in your Content Security Policy.
- Set
convex.auth.trustedClientIpHeaderto a header that your host overwrites with one client IP. A missing or invalid value stops auth requests. - Make sure public traffic can reach the Nuxt server only through that host. Otherwise a client can send the header itself.
- Keep module logging off unless you investigate a specific problem.
- Test sign-in, sign-out, SSR refresh, client navigation, and realtime reconnect.
- Test your Convex access checks with two different users, not only the UI.
- Test each social provider's callback, and the error page for an existing account.
- For MCP, also test consent, PKCE, a wrong client, resource, or scope, revocation, and removing a membership.
Do not let the first sign-in create the signing key, and never delete JWKS rows by hand.
Preview deployments
Better Auth accepts requests only from the exact SITE_URL. A preview with a
random URL therefore cannot sign users in against the production deployment.
Give a stable preview its own Convex deployment, exact SITE_URL, and its own
secrets. Do not share production secrets, cookies, or data with previews, and
do not use wildcard trusted origins.
Health checks
A successful HTML response proves only that Nuxt started. Also check:
- an anonymous Convex query;
- a server-rendered page for a signed-in user, when auth is on;
- a browser subscription that receives a change;
- that the auth proxy rejects unsupported methods.
Read the security model before you open the application to the public.