Cloudflare
On Cloudflare, RoutingCat runs in a Worker, or in Pages’ middleware, before your pages, and several of your account’s and your zone’s settings change what it sees and what your pages run. This page gives each setting a site RoutingCat runs on there needs, with its reason, the deploy key your Cloudflare builds carry, and how a build on your machine deploys production. npx routingcat doctor checks the zone’s features it can see on your pages.
Use Workers Paid
RoutingCat needs Workers Paid. On the Free plan, past 100,000 requests a day, Cloudflare stops running the Worker until midnight UTC:
- a Worker in front of your site’s files answers 429 on every page it runs first on;
- on Pages, Functions count against the same requests, and past them your pages go undecided or fail.
A Worker on a route in front of your own server stops too: its requests fail or, where it fails open, reach your server undecided and unrecorded, for the rest of the day.
The Free plan also stops the work a Worker does after its answer, past its allowance, which loses records silently. status and the dashboard raise the no-records error when the Worker goes quiet.
Workers Paid includes 10 million requests and 30 million ms of CPU a month for $5, then bills $0.30 per million requests and $0.02 per million ms. RoutingCat’s Worker adds about 2 to 5 ms of CPU to a routed page, and every request it runs first on is billed, the beacons your pages send through /_rc and the files run_worker_first names included: at two requests a page view, 30 million views a month cost about $15.60 to $16.80 above the $5, against about $6.60 at one request a view.
A Worker in front of your site’s files runs first on every request, with run_worker_first: ["/*"], and one in front of your server on every path of its route, so each file your pages load is a billed request.
Keep Workers Cache off RoutingCat’s Worker
On Workers, keep Workers Cache off for the Worker RoutingCat runs in. Each of RoutingCat’s pages sets the visitor cookie, so Workers Cache keeps none of them, while Cloudflare bills every request to that Worker, your static files’ included. init and doctor refuse it: remove its cache setting from the Wrangler configuration.
Keep the Wrangler settings init wrote
On Workers, init sets the Wrangler configuration’s main and the version metadata binding CF_VERSION_METADATA, with run_worker_first for a Worker in front of your site’s files, or, for a Worker in front of your server, the route of your production address, as www.lumen.com/*, and Wrangler’s build command, npx routingcat build. Keep them:
run_worker_first, the same list as the Worker’s matcher, and the route each make every page reach RoutingCat first;- the binding names each version you deploy, so each version registers once, at its first request, rather than once per isolate.
doctor names a page the Worker did not run on, and a version registered without its binding.
Turn on the image preload
RoutingCat preloads the main image of the variant each visitor gets, so that no variant shows sooner than another. Cloudflare’s Early Hints cache a page’s preloads per URL, ignoring its query, and would send one visitor’s image to the next. So imagePreload means the same on every host: true sends the preload, false never does, and left out, RoutingCat sends it unless a Cloudflare zone forwarded the request, as one forwards every request your Worker receives, and on Vercel each request that comes through a zone in front. The Worker init writes leaves the preload off, with imagePreload: false, and init’s closing notes say so. doctor can’t see whether Early Hints is on, so the switch is yours: once Early Hints is off for your routed paths, set imagePreload: true, in worker.ts on Workers, in functions/_middleware.ts on Pages, and among the options of middleware() in middleware.ts on Vercel.
Keep Rocket Loader from deferring RoutingCat
Rocket Loader runs a page’s scripts after the page shows, except a script whose tag carries data-cfasync="false", written before its src where it has one. RoutingCat’s head snippet carries it as RoutingCat writes it, so that it shows each visitor’s variants before the page paints: keep it there. Where your build or a tool rewrites that tag, turn Rocket Loader off for your routed paths with a configuration rule in Cloudflare. doctor names a page where Rocket Loader would run the snippet late.
Load Zaraz after RoutingCat
Zaraz’s Auto-inject puts Zaraz’s script first in each page’s <head>, ahead of RoutingCat’s head snippet, which belongs first: an inline script waits for the scripts and stylesheets above it. Turn Zaraz’s Auto-inject off for your routed paths, and load Zaraz after RoutingCat’s snippet. This holds on Vercel behind a Cloudflare zone too. doctor names a page where Zaraz’s script stands before the snippet, whichever host serves it.
Keep your routed pages’ HTML out of cache rules
A cache rule of Cache Everything with an edge TTL on your routed pages’ HTML strips RoutingCat’s cookies and serves one visitor’s page to the next before RoutingCat decides: visitors get a cached copy instead of their own variants, and their visits are not recorded. In Cloudflare’s cache rules, bypass the cache for HTML on your routed paths. Caching your files there is fine. status warns when render beacons outnumber decisions, and doctor names the cache.
Serve every page over HTTPS
Turn on “Always Use HTTPS” for your domain: in Cloudflare, the domain → SSL/TLS → Edge Certificates. RoutingCat’s cookies are Secure, and a browser keeps none of them from an http:// page, so the Worker leaves a request over plain HTTP undecided and unrecorded. doctor asks for your site over plain HTTP and warns when a page answers there.
Read each visitor’s location
On Workers and Pages, RoutingCat reads each visitor’s location from Cloudflare itself, with no setting to turn on. A site on Vercel behind a Cloudflare zone needs the zone’s visitor-location headers instead: in Cloudflare, the domain → Rules → Settings → Managed Transforms → “Add visitor location headers”. Without them, visitors’ local times come from Cloudflare’s edge or from their country, and doctor and status say so.
Give Cloudflare’s builds the deploy key
Cloudflare’s builds carry no token of their own for RoutingCat, so each build proves itself with the site’s deploy key:
- The site’s owner mints it, naming your production branch:
npx routingcat keys deploy --branch main. It is shown once. - You set it as
ROUTINGCAT_DEPLOY_KEYin your project’s build variables, in Cloudflare.
The production key proves production only for a build of its branch, as the build’s CI reports it. So:
- a build of another branch registers as a preview only where it carries the preview key, from
npx routingcat keys deploy --env preview; with the production key, it registers nothing; - a build in your own CI, as one that runs
wrangler deploy, reports its branch from the CI’s own variables on GitHub Actions, GitLab, Bitbucket, CircleCI, Jenkins’ multibranch pipelines, Azure Pipelines and Buildkite; in any other CI, setROUTINGCAT_BRANCHto the branch the build deploys, or the production key is refused; - a build on your own machine proves itself with your sign-in instead, once you ask it for production: see Deploy from your machine.
A build that registers nothing still deploys, and serves your defaults, unrecorded, and its log says why. A build without the key says where to set it, in Cloudflare’s builds:
routingcat: no Vercel or GitHub Actions OIDC token and no ROUTINGCAT_DEPLOY_KEY, so this build cannot identify itself to RoutingCat: it registers nothing, and visitors get your defaults. Run npx routingcat keys deploy --branch <your production branch>, set what it prints as the build variable ROUTINGCAT_DEPLOY_KEY in Cloudflare's build settings, then redeploy.And in your own CI:
routingcat: no Vercel or GitHub Actions OIDC token and no ROUTINGCAT_DEPLOY_KEY, so this build cannot identify itself to RoutingCat: it registers nothing, and visitors get your defaults. Run npx routingcat keys deploy --branch <your production branch>, set what it prints as the secret ROUTINGCAT_DEPLOY_KEY in this build's CI, then redeploy.A build whose key RoutingCat refuses, as the production key on another branch, names the reason. To replace the key, rotate it:
$ routingcat keys deploy --rotate --branch maindeploy key · production · branch main · rotated (op_01m4zbfqr0fgq9d4fhwyhfgbd7) dk_5uXldluoN715TPtdoV4XDrcyLTBDUHO8oTZsyXtOd3HSStsWjo-NnEVdX3ezv_Oby72carY1ZBR7DewxuhcV9q-XMBd9xLPclRkVvpKluDVdsnkXwVZXQ46xmuWN6CZkdVJMdKAh24-0ASy the key it replaces works until 2026-10-16 08:40ZSet it as ROUTINGCAT_DEPLOY_KEY in Cloudflare's build variables. It is shown only when minted or rotated.It proves production only for builds of main, as your CI reports the branch.The previous key keeps working for 24 hours, time to update the build variable. Access and keys covers every key.
Deploy from your machine
To deploy production with Wrangler from your own machine, ask the build for production with ROUTINGCAT_ENV=production: it registers under your sign-in alone, once you confirm what goes live.
Run your site’s build, then ROUTINGCAT_ENV=production npx routingcat build, as hugo && ROUTINGCAT_ENV=production npx routingcat build, then deploy with Wrangler as you do. In front of your own server, wrangler deploy runs routingcat build as its build command: run ROUTINGCAT_ENV=production npx wrangler deploy.
First, the build checks your sign-in and your site, and stops where it can’t go on, registering nothing. It takes the sign-in init or npx routingcat login keeps on your machine, and that alone: a ROUTINGCAT_DEPLOY_KEY in your shell is ignored, and the build says so:
routingcat: ROUTINGCAT_DEPLOY_KEY is ignored: run with ROUTINGCAT_ENV=production on your machine, this build registers under your sign-in, once you confirm its plan.Signed out, the build stops, with exit code 1:
routingcat: You are not signed in to RoutingCat on this machine. Run npx routingcat login.The site’s owner and its members can each deploy this way, and the change is recorded as theirs. It deploys a site init created from its Cloudflare project, or moved to it with --host. A site on Vercel registers from Vercel’s builds, so there the build is refused before it shows any plan:
routingcat: lumen registers production from its build on Vercel project lumen, so a production build on a person's machine is refused: deploy, and that build registers it.Then the build shows its plan, as npx routingcat plan does, and registers nothing until you confirm it. With the plan, it warns you, without stopping, of what the plan can’t show: changes in your folder that aren’t committed, new files included, which it deploys though the change records your last commit; and a branch other than your site’s production branch, the one its production deploy key names, where it has one. Confirmed, the build deploys your folder as it stands: commit first, on your production branch, so that the change’s record matches what you deploy.
routingcat ! This folder has changes that are not committed: the build records commit <commit>, HEAD's, not the code it deploys. (D115)routingcat ! This build is of branch headlines, not the site's production branch, main: confirmed, it goes to production all the same. (D115)At a terminal, it asks Push this plan to production? [y/N]. Yes registers that plan, and the receipt follows:
lumen · this code against production (dp_01m3yfqdsgfkr9jahvnyvk6gaa, commit 4be1c07) · contract 1.0…valid push will accept this
Push this plan to production? [y/N] yroutingcat LIVE home: 4 variants in hero, routing within about 2 minutes (ch_01m40j70srer495wr3vhnyvjca, pb_01m40j70srfb59gbvnmr3nv2t6)routingcat: this deployment embeds bd_b5200f7066f219ee, compiled from this push.Any other answer stops the build, with exit code 1:
Push this plan to production? [y/N] nroutingcat: nothing was registered: the plan was not confirmed, so this build stops here.Where no terminal can ask, as when your agent runs it, the build stops after the plan, with exit code 3 and the plan’s digest:
$ ROUTINGCAT_ENV=production routingcat buildlumen · this code against production (dp_01m3yfqdsgfkr9jahvnyvk6gaa, commit 4be1c07) · contract 1.0…valid push will accept this
routingcat: nothing was registered: this build's plan goes to production once the person confirms it. Show it to them; once they agree, run the build again with ROUTINGCAT_ENV=production ROUTINGCAT_CONFIRM=<digest>.Your agent shows you the plan. Once you agree to it, you or your agent run the build again with ROUTINGCAT_CONFIRM set to that digest, beside ROUTINGCAT_ENV=production: it registers exactly that plan, and the same receipt follows. If your code or production changed in between, the plan has another digest, and the build stops with exit code 1, showing the new plan for you to confirm:
routingcat: nothing was registered: the plan confirmed as <given> has changed since, with the code or production. Show the person this plan; once they agree, run the build again with ROUTINGCAT_ENV=production ROUTINGCAT_CONFIRM=<digest>.A build that stops ends with its exit code before writing what the deploy needs, so a wrangler deploy chained after it with && doesn’t run.
- Production is held to the plan you confirmed. If it changes before the build registers, as with an act someone makes meanwhile, nothing is registered and the log says so: run the build again.
- If RoutingCat can’t be reached, the build doesn’t stop: it ships your defaults, as any build does then, and one line in its log says so. Deploy again once RoutingCat is back.
- In Cloudflare’s builds or a CI,
ROUTINGCAT_ENVchanges nothing: they prove themselves with the deploy key.
Without ROUTINGCAT_ENV=production, a build on your machine with no deploy key and no ROUTINGCAT_BRANCH registers nothing and writes development values, as its log says:
$ routingcat buildroutingcat: a build on your own machine, with no Vercel or GitHub Actions OIDC token and no ROUTINGCAT_DEPLOY_KEY, registers nothing. Under wrangler dev or vercel dev its pages run in development mode, where ?rc= shows the variants you name and nothing is recorded; deployed, they decide nothing. To deploy a site on Cloudflare from this machine, run the build with ROUTINGCAT_ENV=production.Under wrangler dev, its pages run in development mode, where ?rc= shows a variant and nothing is recorded. Deployed, those values decide nothing: your pages show their defaults and record nothing, and npx routingcat doctor names such a page in its selection check:
✗ selection https://lumen.so/ is served by a build made for development on a person's machine, which decides nothing there: visitors get your defaults and nothing is recorded. Deploy a build made in CI, or, on Cloudflare, one run on that machine with ROUTINGCAT_ENV=production. (D46)A deploy script of your own, on a machine or in a CI RoutingCat doesn’t read, is the other path. Set ROUTINGCAT_BRANCH to the branch it deploys, and ROUTINGCAT_DEPLOY_KEY to the production key, from npx routingcat keys deploy --branch <your production branch>: the build proves itself with the key, as a CI does, and registers production for builds of that branch, with no plan to confirm. Without a key, it registers nothing, and the production values it writes decide nothing. With neither variable, a deploy key in your shell is tried as in a CI: a preview key, from npx routingcat keys deploy --env preview, registers a preview, and a production key is refused, since nothing names the branch.