Skip to main content

Vercel preview deployment

This is the first hosted deployment path for apps/web. It is intentionally narrow: get a live Vercel URL for a pull request when someone asks for one, and keep unsafe public states locked. Production hardening belongs here only after an explicit follow-up issue owns it.

Preview deploys are manual. Two independent paths could create one, and both are closed:

  • GitHub Actions. Nothing in Baseline Checks deploys to Vercel, and no push or pull_request event triggers a preview. A preview exists only after a human requests it, as described in On-demand preview deploy.
  • The Vercel Git integration. apps/web/vercel.json sets git.deploymentEnabled to { "**": false, "main": true }, so pushing a commit to any branch other than main creates no automatic Vercel deployment. main is listed explicitly because Vercel deploys a branch when it matches at least one true rule, and production hosting depends on the Git integration for main.

Two caveats on the Git-integration control. Vercel reads vercel.json from the commit being pushed, so a branch that does not yet carry this setting still auto-deploys until it picks the change up from main. And git.deploymentEnabled governs the Git integration only; it does not affect vercel deploy from the CLI, which is how the on-demand workflow and staging-deploy.yml deploy.

Dashboard-side state is not verifiable from this repository. If the Vercel project also has preview deployments or branch tracking configured in its dashboard settings, reconcile them with the setting above so the in-repo file stays the source of truth.

Vercel project

Create or import one Vercel project for this repository:

  • repository: BASIC-BIT/VRDex
  • root directory: apps/web
  • framework preset: Next.js
  • build command: pnpm build:vercel
  • install command: Vercel default pnpm workspace install
  • output directory: Vercel default for Next.js

apps/web/vercel.json records the app-local build command so dashboard and CLI builds use the same deployment validation step.

Run Vercel CLI commands from the repository root once the project root directory is set to apps/web; running from apps/web will make Vercel resolve the app root twice.

Repository variables and secrets

Current recommendation: keep repository Actions variables and secrets reproducible through checked-in workflows/docs first, and provider APIs or CLI scripts where practical. Secret values still belong in GitHub/Vercel/Convex secret stores, but their names, scopes, and recreation path should be documented here.

The on-demand preview workflow deploys a Vercel preview only when all three repository secrets exist:

  • VERCEL_TOKEN
  • VERCEL_ORG_ID
  • VERCEL_PROJECT_ID

The workflow can also deploy a matching Convex preview backend when this optional repository secret exists:

  • CONVEX_DEPLOY_KEY_PREVIEW

If any of the three Vercel secrets is missing, the run fails and names the missing secrets instead of deploying partially. Because the preview is requested explicitly, failing loudly is the correct signal; there is no longer a baseline job that needs to stay green before the hosted project is linked.

VERCEL_TOKEN must be a Vercel account access token created from Vercel account settings. The local Vercel CLI session token from auth.json is not accepted by vercel --token in GitHub Actions and should not be stored as this secret.

When CONVEX_DEPLOY_KEY_PREVIEW exists, the workflow creates or updates the pr-<number> Convex deployment with convex deploy --preview-create before the Vercel build. It writes only the resulting NEXT_PUBLIC_CONVEX_URL as a step output. The project preview deploy key remains confined to GitHub Actions and is never injected into Vercel, written to an artifact, or posted in a PR comment.

The same workflow creates a random, masked persistence-bridge secret for that single run. It writes the secret to the named Convex preview and injects it only into the matching Vercel deployment. The bridge exposes only the guarded dynamic-client persistence mutations needed by DCR and CIMD smoke checks. When the hosted developer-credential gates are also enabled, a second Convex-side capability flag permits the same bridge secret to issue client-credentials access-token records. PR previews do not receive CONVEX_ADMIN_TOKEN; all broader internal operations remain unavailable from the preview web runtime.

When VRDEX_HOSTED_E2E_AUTH_HELPERS=true, VRDEX_HOSTED_E2E_DEVELOPER_CREDENTIALS=true, and the VRDEX_HOSTED_E2E_BROWSER_TOKEN repository secret are all present, that same on-demand preview workflow also creates a separate random Convex E2E secret. It enables the auth helper only on the named Convex preview and injects the matching helper flags, generated Convex secret, and repository browser token only into the matching Vercel preview. This supports reviewed OAuth clients for hosted MCP compatibility evidence without enabling the helper on shared staging or production. The workflow binds SITE_URL to the concrete Vercel deployment URL after deployment.

It does not generate any Clerk configuration, and preview sign-in does not work on its own. Clerk is an external prerequisite: a preview needs an instance, its keys on the Vercel preview, and CLERK_JWT_ISSUER_DOMAIN inherited from the Convex project's preview environment-variable defaults. Since #226 the authenticated flows use Clerk testing tokens rather than a sign-in form, and this workflow does not wire them up. vercel-preview-deploy.yml passes no Clerk E2E secrets, runs no auth spec, and invokes no credential generator, so installing those secrets changes nothing here. Authenticated E2E and generated MCP OAuth credentials run from baseline-checks.yml and deployed-health.yml against the shared staging target — see docs/testing/playwright-visual-preview.md. A preview still needs its own Clerk instance and keys for a human to sign in to it by hand. Separate per-preview runtime material supplies the API token pepper, OAuth client-secret and refresh-token peppers, and OAuth access-token signing key needed by developer credential and client-credentials flows. The token route uses the dedicated preview capability described above instead of an admin key. When any gate is absent, the workflow writes the Convex E2E and preview OAuth token capability switches as false and omits the developer runtime secrets for that preview.

On-demand preview deploy

This is the only path that deploys a Vercel preview. Two workflows implement it:

  • .github/workflows/vercel-preview-comment.yml listens for issue_comment and dispatches the deploy when a pull request comment contains @vrdex preview or /vercel-preview. It requires an author_association of OWNER, MEMBER, or COLLABORATOR, and refuses fork branches and non-open pull requests.
  • .github/workflows/vercel-preview-deploy.yml performs the deploy. Its only triggers are workflow_dispatch and workflow_call, so it cannot fire on push or pull_request. The workflow_call interface exposes a deployment_url output so a calling workflow can capture the link.

So a preview deploy happens only when a person comments @vrdex preview or /vercel-preview on a pull request, or runs the On-Demand Vercel Preview workflow from the Actions tab or gh workflow run. Pushing another commit to a pull request does not redeploy; request a fresh preview when you want one to match new commits.

This path reuses the existing VERCEL_TOKEN, VERCEL_ORG_ID, and VERCEL_PROJECT_ID repository secrets and adds no new ones.

The deploy job runs the full preview pipeline: vercel pull, the optional pr-<number> Convex preview backend with its runtime flags and smoke fixture, a local vercel build, and vercel deploy --prebuilt with the preview-only environment values. The local prebuilt build is what lets the Convex preview URL reach the client bundle through NEXT_PUBLIC_CONVEX_URL; a remote Vercel build would use the project's own Preview environment instead.

Hosted MCP preview smoke

.github/workflows/vercel-preview-deploy.yml also runs the Hosted MCP Preview Smoke job after a successful deploy. It targets <deployment-url>/mcp and is fail-closed: it requires both a deployment URL and a same-branch Convex preview backend, so a pass covers data-backed public reads, Dynamic Client Registration, and Client ID Metadata Document authorization.

Because this workflow is dispatched rather than triggered by pull_request, its jobs do not appear as pull request status checks, so a failure cannot turn the pull request red. Two things compensate. The preview comment links the workflow run so the smoke result stays one click from the pull request. And when the smoke fails, the smoke job posts its own Hosted MCP preview smoke failed comment on the pull request and reacts confused to the requesting comment, so a failure is never silent even though the preview deploy itself succeeded. A later passing run rewrites that same comment in place to Hosted MCP preview smoke passed, so a stale failure never outlives the run that cleared it.

Web environment

Set these in the Vercel project as needed:

  • NEXT_PUBLIC_CONVEX_URL: optional for a shell-only preview; set to the hosted Convex deployment URL for live backend reads.
  • CONVEX_ADMIN_TOKEN: server-only Convex admin/deploy token for route handlers that call internal Convex functions, currently needed by developer credential inventory API routes.
  • VRDEX_REQUIRE_CONVEX_URL=true: optional; use when previews must fail instead of showing missing-backend states.
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY: required for any deployment that should accept sign-in. Production builds fail without them; a shell-only preview may omit both. NEXT_PUBLIC_VRDEX_SUBMISSIONS_AUTH_READY is retired — it gated /submit before web auth existed, and clerkMiddleware protects that route now.
  • NEXT_PUBLIC_POSTHOG_KEY: optional public PostHog project key; BASIC BIT hosted deployments should set this through infra/terraform/vercel for PostHog project 447783.
  • NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com: optional PostHog ingestion host; also managed through infra/terraform/vercel for hosted deployments.

Public API, OAuth, and hosted MCP runtime routes also need the server-side variables inventoried in docs/developers/self-hosting-and-iac.md, including VRDEX_API_TOKEN_PEPPER, VRDEX_OAUTH_CLIENT_SECRET_PEPPER, VRDEX_OAUTH_REFRESH_TOKEN_PEPPER, OAuth access-token signing keys, issuer and resource URLs, and the selected rate-limit backend settings. Keep secret values in Vercel or the deployment secret store; commit only variable names, scope, and rotation guidance.

VRDEX_ENABLE_PREVIEW_PERSISTENCE_BRIDGE and VRDEX_PREVIEW_PERSISTENCE_SECRET are CI-owned preview-only values. Do not set them in shared staging or production environments.

Do not set VRDEX_ENABLE_PLAYWRIGHT_FIXTURES in Vercel. Fixture profiles are for Playwright-only local/CI preview screenshots and must not be exposed from hosted previews.

Hosted dev/staging E2E targets must set these only on the dev/staging environment, not production:

  • VRDEX_ENABLE_E2E_HELPERS=true
  • VRDEX_E2E_BROWSER_TOKEN: same value as the GitHub Actions secret VRDEX_HOSTED_E2E_BROWSER_TOKEN
  • VRDEX_E2E_CONVEX_SECRET: non-empty sentinel matching the Convex deployment secret name
  • VRDEX_ENABLE_E2E_AUTH_HELPERS=true: optional staging-only switch for auth/claim E2E helper routes; keep unset until that flow is intentionally enabled
  • VRDEX_ENABLE_E2E_ADAPTER_HELPERS=true: optional staging-only switch for Discord and VRChat/VRCLinking adapter stubs; keep unset until that flow is intentionally enabled
  • DISCORD_BOT_TOKEN: optional staging-only adapter token when hosted adapter E2E is enabled
  • VRCHAT_PROOF_ADAPTER_BEARER_TOKEN: optional staging-only adapter token when hosted adapter E2E is enabled

Production should keep VRDEX_ENABLE_E2E_HELPERS=false or unset, should keep VRDEX_ENABLE_E2E_AUTH_HELPERS and VRDEX_ENABLE_E2E_ADAPTER_HELPERS unset, and should not set VRDEX_ALLOW_PRODUCTION_E2E_HELPERS unless a human explicitly approves a temporary incident/debug window.

Preview deployment protection must allow unauthenticated reads if the PR preview is meant to be reviewed outside the Vercel dashboard.

Hosted production domain

Locked decision: the hosted BASIC BIT production web app uses the apex domain https://vrdex.net.

Current recommendation: keep both the Vercel project-domain bindings and Route 53 DNS records in infra/terraform/web-domains so production web hosting does not depend on dashboard-only state.

  • primary URL: https://vrdex.net
  • secondary URL: https://www.vrdex.net
  • Vercel project: vr-dex-web
  • Route 53 hosted zone: vrdex.net
  • Route 53 records: A vrdex.net 76.76.21.21 and A www.vrdex.net 76.76.21.21
  • GitHub production smoke variable after DNS is active: VRDEX_PRODUCTION_SMOKE_BASE_URL=https://vrdex.net

Production deployment status events can still report the generated Vercel deployment URL. Scheduled and push-triggered production smoke should use VRDEX_PRODUCTION_SMOKE_BASE_URL so the stable public domain stays under health coverage.

Hosted staging E2E environment

Locked decision: staging is the shared non-production Vercel custom environment for deployed mutation-backed Playwright health checks.

  • target name: staging
  • type: Preview custom environment
  • branch tracking: staging
  • primary URL: https://staging.vrdex.net
  • Vercel environment alias: https://vr-dex-web-env-staging-basicbit.vercel.app
  • deploy command from the repository root: pnpm dlx vercel@54.4.1 deploy --target=staging --yes

DNS for staging.vrdex.net is managed in the Route 53 public hosted zone for vrdex.net:

  • record: staging.vrdex.net CNAME 0d67c3b757aeccf9.vercel-dns-016.com
  • Vercel domain binding: project vr-dex-web, custom environment staging

The staging Vercel environment points at the shared Convex development deployment:

  • NEXT_PUBLIC_CONVEX_URL=https://scrupulous-corgi-247.convex.cloud
  • CONVEX_URL=https://scrupulous-corgi-247.convex.cloud
  • VRDEX_REQUIRE_CONVEX_URL=true
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY
  • VRDEX_ENABLE_E2E_HELPERS=true
  • VRDEX_E2E_BROWSER_TOKEN: sensitive value matching GitHub Actions secret VRDEX_HOSTED_E2E_BROWSER_TOKEN
  • VRDEX_E2E_CONVEX_SECRET: sensitive value matching Convex dev env VRDEX_E2E_CONVEX_SECRET
  • VRDEX_ENABLE_E2E_AUTH_HELPERS=true: enabled for hosted auth/claim E2E
  • VRDEX_ENABLE_E2E_ADAPTER_HELPERS=true: enabled for hosted adapter E2E
  • DISCORD_BOT_TOKEN: staging-only adapter token matching Convex dev env DISCORD_BOT_TOKEN
  • VRCHAT_PROOF_ADAPTER_BEARER_TOKEN: staging-only adapter token matching Convex dev env VRCHAT_PROOF_ADAPTER_BEARER_TOKEN

The staging workflow checks configured Vercel variable names before deployment without printing their values. It reads the custom-environment listing and ignores any branch-specific record whose gitBranch metadata is not main. Every staging deployment requires the Convex URLs, E2E helper contract, deployment environment, and Redis REST rate-limit variables listed in STAGING_BASE_ENVIRONMENT_NAMES in scripts/check-staging-runtime-env.mjs. When repository variable VRDEX_HOSTED_E2E_DEVELOPER_CREDENTIALS=true, the preflight also requires CONVEX_ADMIN_TOKEN, the API and OAuth peppers, the access-token signing key, and the explicit issuer, API, and MCP resource URLs. Missing names fail the deployment before either Convex or Vercel is mutated; value correctness remains a provider bootstrap and hosted-smoke responsibility. Optional defaults such as VRDEX_RATE_LIMIT_REDIS_PREFIX and enforcement switches such as VRDEX_REQUIRE_CONVEX_URL remain documented but are not treated as required names by this preflight.

The non-Redis developer runtime bootstrap is reproducible through pnpm ops:bootstrap-staging-developer-runtime. It requires an ignored env file containing a deployment-scoped CONVEX_DEPLOY_KEY, a Vercel-linked directory, and the process-local VERCEL_API_TOKEN. The command generates independent peppers and an RSA signing key, streams every value to Vercel over stdin, and prints variable names only:

pnpm ops:bootstrap-staging-developer-runtime -- --apply `
--convex-token-env-file <ignored-token-env-file> `
--linked-vercel-directory <vercel-linked-directory>

This command intentionally does not manage the Redis variables. Those remain owned by infra/terraform/rate-limit-redis so the Upstash database, endpoint, token, and Vercel bindings stay one Terraform state boundary.

Convex no longer serves auth callbacks on staging either. The HTTP Actions custom domain https://db.staging.vrdex.net remains verified and scrupulous-corgi-247 selects it as CONVEX_SITE_URL; sign-in runs through the staging Clerk instance.

Current ownership: staging E2E helper variables and non-Redis developer runtime variables are bootstrap-managed Vercel settings; the checked-in bootstrap above recreates the developer runtime subset. infra/terraform/rate-limit-redis owns the shared Redis variables, infra/terraform/web-domains owns production web domains, and infra/terraform/vercel owns hosted PostHog client variables for production, default preview, and configured staging custom environment IDs. Update this document and the matching reproducible owner whenever scopes change, and never commit secret values.

GitHub Actions uses these repository settings for hosted mutation health:

  • variable VRDEX_HOSTED_E2E_BASE_URL=https://staging.vrdex.net
  • variable VRDEX_HOSTED_E2E_EXTENDED_PROFILE_FLOW=true
  • variable VRDEX_HOSTED_E2E_AUTH_HELPERS=true
  • variable VRDEX_HOSTED_E2E_ADAPTER_HELPERS=true
  • variable VRDEX_HOSTED_E2E_DEVELOPER_CREDENTIALS=true: optional; keep unset until staging has the developer token, OAuth app, and OAuth token endpoints under test
  • secret VRDEX_HOSTED_E2E_BROWSER_TOKEN

VRDEX_HOSTED_E2E_BROWSER_TOKEN is a staging-only shared secret that authorizes the bounded E2E helper routes; it is not a user or provider credential. A repository administrator owns it. Store it only as the GitHub Actions repository secret and the matching Vercel staging VRDEX_E2E_BROWSER_TOKEN. Rotate it by replacing the Vercel value first, then the GitHub secret, and rerun staging deployment. Revoke it by disabling the hosted auth-helper variable and removing both stored values.

The Staging Deploy workflow runs after Baseline Checks succeeds on main and can also be run manually. It requires these settings:

  • secret CONVEX_DEPLOY_KEY_DEV: deploys functions/schema to scrupulous-corgi-247
  • secrets VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID: deploy Vercel staging
  • variable VRDEX_HOSTED_E2E_BASE_URL: hosted health target, currently https://staging.vrdex.net
  • secret VRDEX_HOSTED_E2E_BROWSER_TOKEN: browser token for hosted E2E helper calls
  • variable VRDEX_STAGING_CLERK_JWT_ISSUER_DOMAIN: Clerk Frontend API origin of the development instance backing staging, as a full https:// origin. The workflow writes it to Convex as CLERK_JWT_ISSUER_DOMAIN before deploying functions

Those settings behave differently when absent, and the difference is deliberate. Missing any of the first four means this repository is not configured to deploy staging at all, so the workflow writes a skip summary and exits successfully rather than deploying partially. A missing VRDEX_STAGING_CLERK_JWT_ISSUER_DOMAIN fails the job instead. Staging is configured in that case, and is missing a setting its own auth config requires — convex/auth.config.ts reads it on every hosted deployment and the Convex CLI refuses to push without it. Skipping there would report a green workflow while staging silently stopped updating, which is exactly what left staging serving a pre-Clerk build for three days. See convex-environments.md.

When enabled, the workflow audits the Vercel staging variable-name contract, checks that the issuer matches the Clerk publishable key Vercel is about to serve, writes the issuer to Convex, deploys Convex development functions, deploys Vercel staging, re-checks the issuer against what shipped, and runs pnpm test:e2e:hosted against VRDEX_HOSTED_E2E_BASE_URL.

It then runs pnpm test:e2e:hosted:auth-session, the contract asserting that a Clerk session resolves to a verified Convex identity on the deployment that just went out. That runs here rather than from Deployed Health Checks on the push event, because a push fires while these deployments are still going out — so the contract asserted against the previous deployment and reported that as the health of this one. It is gated on VRDEX_HOSTED_E2E_CLERK_AUTH=true with the Clerk keys present: unset and it skips; set with keys absent and it fails, rather than reporting a passing contract over assertions that never ran. The two Playwright runs write to separate report and results directories so the artifact keeps evidence from both.

Because GitHub Actions snapshots secrets and variables when a run starts, rerun the workflow after completing provider bootstrap.

Hosted production environment

Production Vercel hosting uses the same vr-dex-web project with the production domain https://vrdex.net and Convex production deployment superb-pig-954.

  • NEXT_PUBLIC_CONVEX_URL=https://superb-pig-954.convex.cloud
  • CONVEX_URL=https://superb-pig-954.convex.cloud
  • VRDEX_REQUIRE_CONVEX_URL=true
  • VRDEX_ENABLE_E2E_HELPERS=false or unset
  • VRDEX_ENABLE_E2E_AUTH_HELPERS unset
  • VRDEX_ENABLE_E2E_ADAPTER_HELPERS unset

Production must set the same API/OAuth/MCP runtime variables for any enabled developer API, OAuth issuer, or hosted MCP surface.

Convex no longer serves auth callbacks. Clerk hosts sign-in, and CONVEX_SITE_URL on superb-pig-954 stays https://db.vrdex.net for the HTTP Actions the app still uses.

Production auth status, pending cutover:

  • Clerk is the sign-in provider. The production Clerk instance needs its own convex JWT template, its own keys, and its own Google and Discord OAuth credentials pointed at Clerk's callback URLs.
  • The existing Google and Discord OAuth clients are reused by repointing their redirect URIs at Clerk. Do not delete them — the Discord client is also used by community verification, which is independent of sign-in.
  • CLERK_JWT_ISSUER_DOMAIN on Convex must match the issuer of that template.
  • JWT_PRIVATE_KEY and JWKS are retired; Clerk signs tokens now.
  • Session lifetime is Clerk's, documented in docs/backend/auth-sessions.md. The previous 30-day inactivity / 90-day cap contract is not reproduced.

Production authenticated account smoke

The Deployed Health Checks workflow always runs the production read-only route smoke when VRDEX_PRODUCTION_SMOKE_BASE_URL or a deployment status URL is available. It can also run a gated authenticated account smoke when the repository has an explicit stable production base URL and a pre-authenticated production test-account storage state.

Repository settings for the authenticated lane:

  • variable VRDEX_PRODUCTION_SMOKE_BASE_URL=https://vrdex.net: required so auth cookies target the stable public domain instead of a generated Vercel deployment URL
  • VRDEX_PRODUCTION_AUTH_SMOKE_PROVIDER is retired: the account page no longer renders linked providers, because Clerk shows them only inside its own profile modal. The smoke asserts the management affordance instead of a provider label
  • secret VRDEX_PRODUCTION_AUTH_SMOKE_STORAGE_STATE_B64: base64-encoded Playwright storageState JSON from a dedicated production test account that has completed OAuth sign-in

This smoke does not enable production E2E helpers, does not mutate data, and does not store OAuth provider passwords in CI. It checks that the pre-authenticated production account can load /account, sees a sign-out control, and reaches the sign-in management affordance. Refresh the storage-state secret by manually signing in as the dedicated test account and exporting Playwright storage state before the session expires or after Clerk configuration changes.

This lane validates the signed-in production account path only. It deliberately does not verify provider linkage: Clerk renders linked providers inside its own profile modal, and driving a vendor modal from a production smoke would be brittle. Do not cite this lane as evidence that provider linking works. It is also not a full automated provider-login robot; credential entry and fresh consent remain manual or use a provider-approved non-interactive mechanism.

Promoting Production

Vercel's Git integration builds production deployments but does not alias them to vrdex.net. The live site therefore stays on whatever build was last promoted, and a merged change can sit unreleased indefinitely with nothing reporting it.

That is not hypothetical: during the Clerk cutover, Convex production deployed the Clerk backend while vrdex.net kept serving the pre-Clerk bundle, whose sign-in called Convex Auth functions the deploy had just deleted. Production sign-in was broken for hours while every read path returned 200.

Promote with the Promote Production Web workflow:

gh workflow run production-promote.yml -f deployment_url=https://vr-dex-<id>-basicbit.vercel.app

Find the candidate with vercel ls, taking the newest Ready deployment whose environment is Production.

Dispatch is manual on purpose. The preflight can show a build is not obviously broken; it cannot show that releasing it is wanted.

It refuses to promote unless all of the following hold, each corresponding to a way production has broken or nearly broken before:

  • /sign-up returns 200 — a build predating the auth routes cannot sign anyone in

  • /sign-in returns 200 before its body is inspected — an error document still renders the shared auth layout, so its body would otherwise satisfy the content checks

  • the page does not render the auth-unavailable notice — that build was made without Clerk credentials

  • the publishable key decodes to clerk.vrdex.net — tier alone does not prove tenant, and another tenant's pk_live_ key would have Convex reject every token

  • /deployment reports the backend identities the running build actually resolved, and each must match production exactly:

    • data-convex-browserNEXT_PUBLIC_CONVEX_URL, what the browser client uses
    • data-convex-serverCONVEX_URL ?? NEXT_PUBLIC_CONVEX_URL, the precedence convexHttpClient() applies for the API, OAuth, MCP, and account route handlers. It is a separate value, so a build can serve the right data to the browser and the wrong data from its route handlers
    • data-clerk-frontend-api — decoded from the publishable key, naming the Clerk tenant

    Compared as complete URLs, not hosts or origins. Both clients receive the value verbatim, so http:// (whose WebSocket an https origin blocks) and a stray path (which ConvexHttpClient would target) must both fail. A missing Convex URL is only a build warning, so without this a build with no backend is promotable

  • clerk.vrdex.net/v1/environment returns 200 — the instance must be able to issue tokens

  • that response has user_settings.actions.delete_self false — the promoted build exposes Clerk's profile surface, and VRDex cannot reconcile a deleted identity until #227

Afterwards it compares the ?dpl= deployment id on vrdex.net against the requested deployment, so an alias that did not move fails rather than passing on a route check the previous release would also satisfy.

Validation

The Vercel build runs pnpm build:vercel, which executes apps/web/scripts/check-vercel-env.mjs before next build.

The validation fails when:

  • Playwright fixtures are enabled.
  • Any E2E helper switch is enabled for a production Vercel build.
  • A Clerk key is missing: both are required for every production build, and for any build with VRDEX_REQUIRE_CONVEX_URL=true.
  • Only one Clerk key is set. Both, or neither — one key alone mounts ClerkProvider and selects clerkMiddleware with no server-side credential, which fails at runtime rather than falling back to unconfigured auth.
  • A Clerk key is from the wrong tier: production requires pk_live_/sk_live_, every other Vercel environment requires pk_test_/sk_test_, so a preview cannot authenticate against the production tenant.
  • A rate-limit variable is missing or invalid for a production build: VRDEX_RATE_LIMIT_STORE must be redis-rest or upstash, and the REST URL must be https and not a local backend.
  • NEXT_PUBLIC_CONVEX_URL is invalid.
  • NEXT_PUBLIC_CONVEX_URL points at localhost during a Vercel build.
  • VRDEX_REQUIRE_CONVEX_URL=true and NEXT_PUBLIC_CONVEX_URL is missing.
  • NEXT_PUBLIC_POSTHOG_HOST is invalid or points at a local backend during a Vercel build.

Live smoke check

After a preview deployment, visit:

  • / for the public shell
  • /deployment for Vercel environment and commit metadata
  • /submit to confirm signed-out users are routed to sign in before writing

The on-demand preview workflow posts an On-demand Vercel preview comment with the preview URL, the /deployment URL, and a link to the workflow run that carries the hosted MCP smoke result.