# Vercel

> One command links the project, pushes your environment, deploys, and asks the deployment whether it came up configured.

Page: https://sdk.chatfuel.com/docs/deploy/vercel
Markdown: https://sdk.chatfuel.com/docs/deploy/vercel.md

`npm run deploy` is `scripts/deploy-vercel.mjs` inside your own project, driving the Vercel CLI.
There is no GitHub repo to connect and no dashboard to visit, and re-running it is the normal way
to ship an update — every step is written to be run again.

```bash
npm run deploy
```

<Steps>
  <Step>
    ### It finds the Vercel CLI, or fetches it [#it-finds-the-vercel-cli-or-fetches-it]

    From this machine the deploy needs `api.vercel.com`, and `registry.npmjs.org` as well when the CLI
    is not already installed. Nothing else: the build downloads what it needs on Vercel's side, and
    telemetry upload is switched off for every call the script makes, so a blocked telemetry host
    cannot stop a deploy. If something does not get through, the script names the host it could not
    reach rather than blaming the build.
  </Step>

  <Step>
    ### It signs you in [#it-signs-you-in]

    Through the CLI's own login.
  </Step>

  <Step>
    ### It asks what to call the project, then links the directory [#it-asks-what-to-call-the-project-then-links-the-directory]

    The name is the address (`<name>.vercel.app` when it is free), which is why it is asked rather than
    taken from the directory name. If you already have a project by that name it says so and offers to
    pick another — `vercel link` would otherwise link to it and deploy over whatever is live there.
    Pass `--project <name>` or set `VERCEL_PROJECT_NAME` to skip the question.
  </Step>

  <Step>
    ### It pushes the variables from `.env` [#it-pushes-the-variables-from-env]

    Each one goes up with `vercel env add --force`, to **production** and **preview**. `--force`
    overwrites the value for that target instead of adding a second one. Development stays local.
    Variables that exist on Vercel but not in your `.env` are left alone; the script never deletes.
  </Step>

  <Step>
    ### It deploys to production [#it-deploys-to-production]
  </Step>

  <Step>
    ### It asks the deployment whether it came up configured [#it-asks-the-deployment-whether-it-came-up-configured]

    One request to `/chatfuel/healthz`, the proxy's health route reached through the rewrite. An
    answer of `ok` prints the gate state and the deploy is done. A redirect means Deployment
    Protection answered instead — the script reports where it was sent rather than following it. A
    non-JSON answer means something other than the proxy replied, and it points at the routes in
    `vercel.json`. A proxy that is up but misconfigured names its problems and tells you to check
    `vercel env ls` and run the command again.
  </Step>
</Steps>

The URL it prints at the end is the public one. `vercel deploy` prints the *deployment* URL, which
under Vercel's default Deployment Protection sits behind an SSO wall — fine for you, a dead link
for everybody else — and which alias is which is not derivable from the name. So the script asks
each in turn and hands you the first that answers without the wall. Only if none of them do is
there anything to change in the dashboard, and it says so.

If you installed the `auth` module, one thing the script cannot do for you: Supabase has to be told
the deployed origin may receive auth redirects, or sign-in on that domain fails. It prints the URL
to add.

## What goes up, and what stays home [#what-goes-up-and-what-stays-home]

<Callout type="info">
  `.env` is never uploaded (`.vercelignore`), so the Vercel project environment is the only source
  of configuration there — a build cannot silently read a stale local file instead.
</Callout>

**Sensitive** — Vercel's default for production and preview, so they cannot be read back out of the
dashboard: `CHATFUEL_TOKEN`, `SUPABASE_SERVICE_ROLE_KEY`, `PUBLISHING_SECRET`, `ADMIN_PASSWORD`.
None of them carries a `VITE_` prefix, so Vite cannot bake them into the client bundle even in
principle.

**Readable in the dashboard** — pushed with `--no-sensitive`: `CHATFUEL_API_BASE`,
`CHATFUEL_WORKSPACE_ID`, `VITE_SUPABASE_URL`, `VITE_SUPABASE_ANON_KEY`,
`VITE_CHATFUEL_WORKSPACE_ID`, `VITE_APP_NAME`, `VITE_APP_LOGO`, `PUBLIC_URL`. A workspace id or a
project URL that nobody can look at is a debugging tax for no security.

`VITE_*` values are read twice — baked into `dist/` at build time and read again by the proxy at
runtime — which is why they are pushed as project variables rather than passed to the build alone.

## What `vercel.json` does [#what-verceljson-does]

```json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "framework": "vite",
  "buildCommand": "npm run build:client",
  "outputDirectory": "dist",
  "rewrites": [
    { "source": "/chatfuel/:cfpath*", "destination": "/api/chatfuel" },
    { "source": "/(.*)", "destination": "/index.html" }
  ],
  "functions": { "api/chatfuel.ts": { "maxDuration": 300 } }
}
```

The first rewrite puts every `/chatfuel/*` request on the proxy function. The second is the SPA
fallback that every host needs. `maxDuration: 300` is the function's ceiling.

`api/chatfuel.ts` exports an `http.Server`, which is what lets Vercel Functions accept the
WebSocket upgrade for subscriptions. A plain rewrite straight to `panel.chatfuel.com` would be
simpler and is not usable: a rewrite forwards the browser's headers and cannot add one, so the
Chatfuel token would have to be sent by the client.

It is one static filename rather than a catch-all `api/chatfuel/[...path].ts` because Vercel's
zero-config `api/` directory compiles a catch-all into a route that matches a single path segment —
`/chatfuel/graphql` survives that and `/chatfuel/auth/provision` does not. So the requested path
travels as the `cfpath` query parameter and is put back before the proxy core sees it. That
parameter name is a contract between `vercel.json` and `api/chatfuel.ts`; the two must not drift.

## A WebSocket lives no longer than the function [#a-websocket-lives-no-longer-than-the-function]

A subscription's socket closes when the function hits its duration limit — 300 s on Hobby — and
the client reconnects and refetches. Nothing is lost; that is what the reconnect refetch exists
for. On [your own server](/docs/deploy/node-server) the socket is unbounded.

## Root only [#root-only]

Vercel serves the app from the domain root. If you need `https://example.com/app/`, run the
bundled Node server yourself — see [Serving from a sub-path](/docs/deploy/sub-path).

## Shipping an update [#shipping-an-update]

The same command. The link is reused, the project is never re-created, variables are overwritten
rather than duplicated, and the production address moves to the new deployment. Local edits are
not live until you run it.
