Skip to content

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.

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.

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, run_worker_first and the version metadata binding CF_VERSION_METADATA. Keep them:

  • run_worker_first lists the same paths as the Worker’s matcher, so that every page reaches RoutingCat before your files are served;
  • 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.

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:

  1. The site’s owner mints it, naming your production branch: npx routingcat keys deploy --branch main. It is shown once.
  2. You set it as ROUTINGCAT_DEPLOY_KEY in 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, set ROUTINGCAT_BRANCH to 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 main
deploy key · production · branch main · rotated (op_01m4zbfqr0fgq9d4fhwyhfgbd7)
dk_5uXldluoN715TPtdoV4XDrcyLTBDUHO8oTZsyXtOd3HSStsWjo-NnEVdX3ezv_Oby72carY1ZBR7DewxuhcV9q-XMBd9xLPclRkVvpKluDVdsnkXwVZXQ46xmuWN6CZkdVJMdKAh24-0ASy
the key it replaces works until 2026-10-16 08:40Z
Set 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 ROUTINGCAT_ENV=production npx nuxt build, then deploy with Wrangler as you do.

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] y
routingcat 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] n
routingcat: 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 nuxt build
lumen · this code against production (no production deployment yet) · 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_ENV changes 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:

$ nuxt build
routingcat: 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.

Next