Skip to content
ChatfuelSDK
Deployment

Your own server

Build both halves, run one Node process, and read /healthz first when anything looks wrong.

npm start is one process — node server/dist/entry.js. It serves the built client out of dist/ and proxies Chatfuel traffic with your token injected server-side, on one node:http server with no framework. Anything you put in front of it is yours to choose.

Building and starting

npm run build     # client → dist/, then server → server/dist/entry.js
npm start         # node server/dist/entry.js   (PORT, default 3000)

npm run build:client and npm run build:server build the halves separately, which is what a CI job usually wants.

What answers a request

In order: /healthz, then the Chatfuel routes (POST /chatfuel/graphql, WS /chatfuel/graphql, /chatfuel/api/*), then static files from dist/.

Static serving is GET/HEAD only and contained inside dist/.. and absolute segments are refused before any filesystem call. Hashed /assets/* are public, max-age=31536000, immutable, index.html is no-cache, x-content-type-options: nosniff goes on everything, and an unknown path that is not asset-shaped gets index.html, because it is a route. A path under /assets/, or one whose extension is a type the server serves, keeps its 404 — a renamed bundle must never receive HTML. An upgrade to anything but the relay path is destroyed, and SIGTERM closes.

Read /healthz first

/healthz answers {"ok":true,"auth":"on|off|misconfigured"}. It is the first thing to read when a deployment misbehaves, because it tells you which half of the configuration the server actually came up with.

authWhat the server is doing
onVITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are both set. Every proxied request must carry the caller's session, and the bot fence is live.
offNeither is set. Open mode — the single-user, pre-auth behaviour.
misconfiguredOne is set and the other is not. The proxy fails closed: every request answers 500 ProxyAuthMisconfigured rather than guessing which half you meant.

The server prints the same mode on its startup line. off on a deployment you built with Supabase is the other half of the same problem: the client renders a sign-in screen the server knows nothing about — see the rule that catches everyone once.

The port, and the .env beside the app

PORT, default 3000; the host defaults to 0.0.0.0.

A .env next to the app is loaded on start, and real environment variables win over it. That is what makes npm run build && npm start behave like npm run dev on your own machine, and it is why the file is safe to leave in a deployed image that sets real variables.

Whatever you put in front

It has to do the same one thing the bundled server already does: answer an unknown path with index.html. On nginx that is try_files $uri /index.html;. Without it a reload of /deals/board is a 404 while clicking to the same page works — the failure looks like a routing bug and is not one.

WebSockets are unbounded here

There is no function duration to close a subscription's socket. That is the one behaviour that differs from Vercel, where a socket lives at most 300 s on Hobby and the client reconnects.

On this page