Skip to content
ChatfuelSDK
Deployment

Vercel

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

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.

npm run deploy

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.

It signs you in

Through the CLI's own login.

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.

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.

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.

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

.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.

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

{
  "$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 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 the socket is unbounded.

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.

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.

On this page