---
title: "Deployment"
description: "Deploy Convex functions before the Nuxt app, set the URLs at build or run time, and ship to Vercel, Cloudflare Workers or a Node server."
canonical_url: "https://nuxt-convex.onmax.me/nuxt-module/deployment"
---
# Deployment

> Deploy Convex functions before the Nuxt app, set the URLs at build or run time, and ship to Vercel, Cloudflare Workers or a Node server.

A release has two parts: your Convex functions, deployed with the Convex CLI, and the Nuxt app, deployed wherever Nitro runs. Deploy Convex first, so the new app never calls functions that don't exist yet.

## 1. Deploy Convex from CI

Create a production deploy key in the Convex dashboard (your project's production deployment, **Settings**, **Deploy keys**) and store it as the `CONVEX_DEPLOY_KEY` secret of your CI or hosting provider. Then:

```bash [Terminal]
npx convex deploy
```

`npx convex deploy` typechecks your functions, regenerates `convex/_generated/`, and pushes functions, schema and indexes to the deployment the key belongs to.

Set the environment variables your functions read on the production deployment once, with `--prod`:

```bash [Terminal]
npx convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)" --prod
npx convex env set SITE_URL https://your-app.com --prod
```

## 2. Point the app at the deployment

The app needs the deployment URL, and with the Better Auth proxy, the site URL. You can provide them at build time or at run time:

<table>
<thead>
  <tr>
    <th>
      When
    </th>
    
    <th>
      Variables
    </th>
    
    <th>
      Notes
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Build
    </td>
    
    <td>
      <code>
        CONVEX_URL
      </code>
      
      , <code>
        CONVEX_SITE_URL
      </code>
    </td>
    
    <td>
      Baked into the build. <code>
        NUXT_PUBLIC_CONVEX_URL
      </code>
      
       and <code>
        VITE_CONVEX_URL
      </code>
      
       are read too. <code>
        .env.local
      </code>
      
       is only read by <code>
        nuxt dev
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Run time
    </td>
    
    <td>
      <code>
        NUXT_PUBLIC_CONVEX_URL
      </code>
      
      , <code>
        NUXT_PUBLIC_CONVEX_SITE_URL
      </code>
    </td>
    
    <td>
      Override the built values, so one build can run against staging and production
    </td>
  </tr>
  
  <tr>
    <td>
      Run time, with Better Auth
    </td>
    
    <td>
      <code>
        NUXT_PUBLIC_SITE_URL
      </code>
    </td>
    
    <td>
      The app's own public URL, used by <code>
        @nuxtjs/better-auth
      </code>
      
       during SSR
    </td>
  </tr>
</tbody>
</table>

The site URL is derived from the deployment URL (`.convex.cloud` becomes `.convex.site`), so you only need `CONVEX_SITE_URL` for a custom domain or a self-hosted backend. If you switch the deployment at run time and baked a site URL into the build, override both.

## Vercel

Let the Convex CLI run the build, so it deploys first and hands the URL to Nuxt:

1. In Vercel, add `CONVEX_DEPLOY_KEY` to the Production environment.
2. Set the build command to:

```bash [Build Command]
npx convex deploy --cmd 'npm run build' --cmd-url-env-var-name CONVEX_URL
```

The build runs with `CONVEX_URL` (named by `--cmd-url-env-var-name`) and the deployment's site URL set, both of which `nuxt-convex` reads. With a preview deploy key in the Preview environment, the same command deploys to a Convex preview deployment for each branch. Nuxt picks the `vercel` Nitro preset by itself.

## Cloudflare Workers

The [playground](https://github.com/onmax/nuxt-convex/tree/main/playground) runs on Workers and its [deploy workflow](https://github.com/onmax/nuxt-convex/blob/main/.github/workflows/playground.yml) is a working example:

1. `pnpm exec convex deploy` with `CONVEX_DEPLOY_KEY`
2. `nuxi build` with the `cloudflare-module` preset
3. `wrangler deploy`
4. a smoke test: the page contains the deployment URL, and an authenticated API route answers 401 to an anonymous request

The runtime variables live in the Wrangler config (`wrangler.json` or `wrangler.jsonc`):

```json [wrangler.json]
{
  "vars": {
    "NUXT_PUBLIC_CONVEX_URL": "https://happy-otter-123.convex.cloud",
    "NUXT_PUBLIC_CONVEX_SITE_URL": "https://happy-otter-123.convex.site"
  }
}
```

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['nuxt-convex'],
  nitro: {
    preset: 'cloudflare-module',
  },
})
```

## Node

Build once, then pass the URLs when you start the server:

```bash [Terminal]
npx convex deploy
npx nuxi build
NUXT_PUBLIC_CONVEX_URL=https://happy-otter-123.convex.cloud node .output/server/index.mjs
```

The same applies to Docker images and any other Node host: keep the URL out of the image and set it in the container's environment.

## GitHub Actions

A minimal workflow for any host with a CLI deploy:

```yaml [.github/workflows/deploy.yml]
name: deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm
      - run: pnpm install --frozen-lockfile

      # Convex first, so the new app never calls functions that aren't deployed
      - run: pnpm exec convex deploy
        env:
          CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}

      - run: pnpm build
        env:
          CONVEX_URL: ${{ vars.CONVEX_URL }}

      # your host's deploy command
```

## Before you go live

- Signed-in pages aren't cached by a shared cache: don't add `cache`, `swr` or `isr` route rules to them. See [Caching](https://nuxt-convex.onmax.me/nuxt-module/authenticated-ssr#caching).
- If you use the storage scaffold, add ownership checks. See [File Storage](https://nuxt-convex.onmax.me/nuxt-module/file-storage).
- With Better Auth, the Convex deployment's `SITE_URL` (or whatever your `convex/auth.ts` reads) must be the public URL of the Nuxt app, because auth requests reach Convex through its proxy.
- Rotating `BETTER_AUTH_SECRET` invalidates the stored signing keys. See [Troubleshooting](https://nuxt-convex.onmax.me/troubleshooting#failed-to-decrypt-private-key).

## Related pages

- [Local Development](https://nuxt-convex.onmax.me/getting-started/local-development)
- [Module Configuration](https://nuxt-convex.onmax.me/api-reference/module-configuration#url-resolution)
- [Security](https://nuxt-convex.onmax.me/nuxt-module/security)


## Sitemap

See the full [sitemap](https://nuxt-convex.onmax.me/sitemap.md) for all pages.
