Today Platform Web — Dev Docs
Workflow

Deployment

apps/web deploys through GitHub Actions prebuilt deploys; docs sites deploy through Vercel's Git Integration.

The repo's active deploy paths are split by ownership:

  • apps/web is owned by GitHub Actions prebuilt deploys.
  • apps/dev-docs and apps/webview-bridge-docs are owned by Vercel's Git Integration.
Vercel projectSource pathWorkflowRepo variableOwner
today-webapps/webdeploy-web.yml, deploy-web-lane-staging.yml, promote-web-production.ymlVERCEL_PROJECT_ID_WEBActions
today-dev-docs (Vercel project: web-dev-docs)apps/dev-docs(none)(n/a)Vercel Git
webview-bridge-docsapps/webview-bridge-docs(none)(n/a)Vercel Git

The admin console (today-admin Vercel project, admin.today.ai) is deployed from todayai-labs/today-admin.

For the underlying conceptual model, see Architecture -> Vercel deploy model.

Web Environments

FlowGit ref / actionGitHub environmentVercel targetPublic URLs
Dev / PR previewdev push or PRweb-previewpreviewunique/contextual *.dev.preview.todayai.dev; merged dev also gets todayai.dev
Lane staging candidaterelease/web-* pushweb-lane-stagingcustom staging targetunique/contextual *.staging.preview.today.ai aliases plus https://staging.today.ai
Production candidate reviewmanual promote workflowweb-productionproduction-mode candidate with --skip-domainunique and workflow aliases under *.prod.preview.today.ai; active hotfix also uses hotfix.prod.preview.today.ai
Production promoteapproved workflow stepn/avercel promote of the production candidatethe candidate's preview aliases plus https://today.ai

Use the terms precisely:

  • lane staging is staging.today.ai, tied to backend lane routing. Its web build uses Vercel's custom staging target and injects APP_ENV=production plus NEXT_PUBLIC_TRAFFIC_LANE=staging, so the app keeps production semantics while browser clients and BFF upstream fetches send X-Traffic-Lane: staging.
  • production candidate is a separate production build from the same release SHA without NEXT_PUBLIC_TRAFFIC_LANE. It is the only artifact that may be promoted to today.ai.

Because NEXT_PUBLIC_TRAFFIC_LANE is baked into browser JavaScript, promoting a lane staging build to production would leak the staging lane into production traffic. The production candidate therefore rebuilds from the release SHA.

Web Preview Aliases

Every Actions-owned web deployment receives an opaque random alias that never moves and a contextual alias that can move as its PR or workflow is updated:

  • dev and PR builds use <random>.dev.preview.todayai.dev plus either web-pr-<number>.dev.preview.todayai.dev or web-run-<id>.dev.preview.todayai.dev;
  • after a PR merges, the resulting dev push keeps those two custom preview URLs and Vercel moves the existing todayai.dev branch alias to that deployment;
  • lane-staging builds use <random>.staging.preview.today.ai plus either web-pr-<number>.staging.preview.today.ai or web-run-<id>.staging.preview.today.ai;
  • production candidates use <random>.prod.preview.today.ai plus web-run-<id>.prod.preview.today.ai;
  • after immutable candidate smoke, the dedicated hotfix workflow also moves hotfix.prod.preview.today.ai to the only active hotfix candidate;
  • PR comments show both aliases on separate labeled lines. Both initially point to the latest successful deployment. When a newer deployment succeeds, the random snapshot alias stays unchanged while the contextual latest pointer moves forward. GitHub environment links use the contextual alias, while smoke tests use the random alias to verify wildcard DNS, certificate, and routing for that deployment.

Lane staging moves staging.today.ai only after both preview aliases exist and the random alias passes smoke. Production keeps today.ai unchanged through candidate review; vercel promote adds the production domain only after the approval gate.

Ad Hoc Web Candidates

Three manual workflows can build any branch, tag, or full commit SHA from this repository without moving a stable environment domain:

  • Deploy Web Dev Candidate builds with Vercel Preview settings and assigns *.dev.preview.todayai.dev aliases;
  • Deploy Web Staging Candidate builds with the custom Vercel staging target plus APP_ENV=production and NEXT_PUBLIC_TRAFFIC_LANE=staging and assigns *.staging.preview.today.ai aliases;
  • Deploy Web Prod Candidate builds with production settings, removes the staging lane, and assigns *.prod.preview.today.ai aliases.

Run the workflow itself from dev, then provide the requested source in its source_ref input. This keeps the workflow definition and deployment scripts on the trusted trunk while checking out the candidate source into a separate directory. The input accepts a branch, tag, or full commit SHA from this repository; pull-request refs and external repositories are not accepted.

Every manual candidate gets an immutable random URL and a moving web-run-<run-id> URL. The candidate workflows never assign todayai.dev, staging.today.ai, or today.ai, and never call vercel promote. Production candidates enter the web-production GitHub Environment before source checkout or build. Configure required reviewers on that environment to gate access to production configuration and secrets. Candidate builds disable source-map uploads to avoid exposing observability upload credentials to arbitrary source revisions.

Staging candidates pull the custom Vercel staging configuration. Keep the web-lane-staging GitHub Environment protected because arbitrary source still runs with staging-scoped credentials and service access.

Ad hoc staging and production candidates are inspection artifacts only. They do not satisfy release ownership, frozen-SHA, CI, E2E, approval, tagging, or backmerge requirements and cannot replace the normal release workflows.

Web Release Pipeline

See Web release flow for the operator checklist. The workflow files map to these stages:

  1. cut-web-release.yml creates a temporary release/web-* branch from dev or another explicit production-tag source. Weekly cuts require the current dev tip and an idle staging lifecycle; replacing an intentionally abandoned release requires its exact branch and full SHA and creates a permanent web-abandoned-* marker. Same-day replacement branches use .2, .3, and so on. Emergency hotfix cuts require the current production web-prod-* source SHA.
  2. deploy-web-lane-staging.yml waits for CI, builds from the custom Vercel staging target with APP_ENV=production and NEXT_PUBLIC_TRAFFIC_LANE=staging, deploys with --target=staging, and assigns unique and contextual staging-preview aliases. It smokes the unique alias before moving staging.today.ai. All staging deploys share one concurrency group and pass an active-release ownership guard before changing the stable staging alias.
  3. promote-web-production.yml requires the aggregate CI Success result and, on the normal path, exact-SHA staging deployment, build metadata, and Full E2E evidence. It then builds the production candidate without lane env, pauses on the web-production approval gate, verifies the release branch SHA did not move, promotes to today.ai, archives the exact SHA with a protected web-prod-* tag, opens the required reconciliation PRs, and deletes the promoted source branch after those refs and production metadata are verified.
  4. promote-web-hotfix.yml is the production-candidate-first emergency path. It always skips stable lane staging, assigns hotfix.prod.preview.today.ai only after immutable candidate smoke, and reuses the same production approval, promote, tag, and reconciliation engine. An optional staging candidate remains isolated and never changes staging.today.ai.
  5. merge-web-release-reconciliation.yml is the only merge-commit exception for backmerge/web-* and backport/web-* PRs. It verifies approval, the immutable production tag, source-side topology, and CI on the current potential merge, then uses the Today Platform CI App to compare-and-set fast-forward the target to the exact two-parent reconciliation merge.

Weekly production promotion cannot skip stable staging evidence. Use the dedicated hotfix workflow when production must be repaired without waiting for the weekly lane.

Required Vercel Setup

For today-web:

  • Actions own CLI deploys. Keep Vercel Git auto-deploy disabled for the project.

  • today.ai is the production domain.

  • staging.today.ai is an Actions-owned alias for lane staging deployments.

  • *.dev.preview.todayai.dev must route to Vercel and have an active wildcard certificate for dev and PR aliases.

  • *.staging.preview.today.ai must route to Vercel and have an active wildcard certificate for ad hoc staging candidates and release lane-staging aliases.

  • *.prod.preview.today.ai must route to Vercel and have an active wildcard certificate for production-candidate aliases.

  • When today.ai and todayai.dev remain on external authoritative DNS, configure each wildcard route and ACME delegation in its corresponding zone:

    # todayai.dev zone
    CNAME  *.dev.preview                     -> cname.vercel-dns-0.com
    NS     _acme-challenge.dev.preview      -> ns1.vercel-dns.com
    NS     _acme-challenge.dev.preview      -> ns2.vercel-dns.com
    
    # today.ai zone
    CNAME  *.prod.preview                    -> cname.vercel-dns-0.com
    NS     _acme-challenge.prod.preview     -> ns1.vercel-dns.com
    NS     _acme-challenge.prod.preview     -> ns2.vercel-dns.com
    CNAME  *.staging.preview                 -> cname.vercel-dns-0.com
    NS     _acme-challenge.staging.preview  -> ns1.vercel-dns.com
    NS     _acme-challenge.staging.preview  -> ns2.vercel-dns.com

    The wildcard certificates for *.prod.preview.today.ai, *.staging.preview.today.ai, and *.dev.preview.todayai.dev are independent.

  • The custom Vercel staging environment should not track main; lane staging is built by GitHub Actions from release/web-* with vercel pull --environment=staging, vercel build --target=staging, and vercel deploy --prebuilt --target=staging. The --skip-domain option is production-only; the workflow assigns staging aliases explicitly after the immutable candidate passes smoke.

  • The custom staging environment must set APP_ENV=production, NEXT_PUBLIC_APP_URL=https://staging.today.ai, production API/auth URLs, and NEXT_PUBLIC_TRAFFIC_LANE=staging. This keeps production application semantics while separating the Vercel deployment target and configuration.

  • Do not set X-Traffic-Env or LANE_STAGING_TRAFFIC_ENV; the web lane contract is NEXT_PUBLIC_TRAFFIC_LANE=staging at build time and X-Traffic-Lane: staging on requests.

  • The Web Vercel project is the source of truth for public PostHog ingestion tokens. Development/Preview provide NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN_DEV; Staging/Production provide NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN_PROD. Web and Desktop builds pull the corresponding environment and fail closed when the selected value is absent. Dev/test Desktop packages pull both Preview DEV and Production PROD because those packages can switch runtime environment; fixed staging/prod packages pull only their corresponding PROD profile. Desktop CI transfers only an allowlisted public artifact from a trusted config-only job; pull-request code never receives the broader Vercel credential.

GitHub needs these shared variables/secrets:

  • vars.VERCEL_ORG_ID
  • vars.VERCEL_PROJECT_ID_WEB
  • secrets.VERCEL_TOKEN
  • vars.TODAY_PLATFORM_CI_APP_ID
  • secrets.TODAY_PLATFORM_CI_APP_PRIVATE_KEY
  • optional source-map upload secrets: POSTHOG_API_KEY, POSTHOG_PROJECT_ID_DEV, POSTHOG_PROJECT_ID_PROD, SENTRY_AUTH_TOKEN

PR Previews

deploy-web.yml handles only dev and PR preview deployments. It uses Vercel preview env vars, deploys a prebuilt preview artifact, assigns immutable random and moving contextual aliases under *.dev.preview.todayai.dev, and comments the contextual URL on PRs. A merged PR subsequently triggers the dev push deploy; Vercel's existing branch-domain assignment moves todayai.dev to that latest successful dev deployment.

Docs Sites

apps/dev-docs and apps/webview-bridge-docs deploy through Vercel's Git Integration. Do not add GitHub Actions deploy workflows for them unless the app is explicitly migrated to Actions-owned prebuilt deploys and its vercel.json disables Vercel Git deploys at the same time.

Rollback

Vercel keeps every deploy. Runtime rollback is a human-selected Vercel rollback or promotion of a previous deployment.

For code-level rollback, create a revert PR against dev, let it land through the merge queue, cut a new release/web-* branch, and run the normal promote flow. The durable production archive is the web-prod-* tag, not main.

On this page