Troubleshooting
Find your symptom, then its fix. Each message RoutingCat prints is explained in the messages reference, and npx routingcat doctor, read check by check, names most of them.
init stops
init says |
Do this |
|---|---|
| Nuxt is not installed | Install the app’s packages, then run init again. |
| Nuxt is below 4.6 | Upgrading Nuxt is your call: npx nuxt upgrade --dedupe. A Nuxt 2 app built by a server takes another route, since that command doesn’t upgrade it: build it static, with target: 'static' in nuxt.config and nuxt generate, which RoutingCat sets up as a site with templates, or move to Nuxt 4 with Nuxt’s migration guide. |
| It can’t tell where the site deploys, with exit code 2 | Run init again with --host vercel, --host cloudflare-workers or --host cloudflare-pages. |
| The folder isn’t linked to Vercel, or the Vercel CLI is signed out | Run npx vercel link or npx vercel login, then init again. |
| Wrangler is signed out, or reaches several Cloudflare accounts | Run npx wrangler login yourself, or set account_id in the Wrangler configuration to the account the site deploys from, then run init again. |
| Nitro’s preset isn’t your host’s | Name your host’s preset where the message says your app names one, in nuxt.config, your environment or your build script: cloudflare_module on Workers, cloudflare_pages on Pages. Then run init again. |
| Workers Cache is on for the Worker RoutingCat runs in | Remove its cache setting from the Wrangler configuration, then run init again. |
| It needs your production address, with exit code 2 | On Cloudflare, run init again with your address: --origin https://lumen.so. |
No project init recognizes, or several sites |
Run init in your Nuxt app’s own folder. |
| Installing the package failed | Install routingcat yourself, then run init again. |
| The sign-in isn’t approved yet, with exit code 3 | Approve the link, then run init again: it continues where it stopped. |
| The Vercel project belongs to a site you’re not a member of | Ask that site’s owner to add you. |
| A file is too unusual to edit | init prints the exact edit: make it, then run npx routingcat doctor, which checks it. |
Running init again is always safe: it does what’s missing and never overwrites your edits.
The build registers nothing
A build that registered prints RoutingCat’s receipt in its log, starting routingcat LIVE, NOT LIVE or registered. Without it, the deployment shows your defaults and records nothing, conversions included. The build log says why:
| The build log says | Do this |
|---|---|
| No Vercel token and no deploy key | On Vercel, turn on Settings → Security → Secure backend access with OIDC federation, then redeploy. On Cloudflare, set ROUTINGCAT_DEPLOY_KEY in your project’s build variables, from npx routingcat keys deploy --branch <production branch>. |
| RoutingCat can’t be reached | Redeploy once it’s back. |
| The build token or deploy key was refused | The line gives the reason. The production deploy key serves only builds of its branch, as their CI reports it: build your production branch in Cloudflare’s builds or GitHub Actions, and give other branches’ builds the preview key, from npx routingcat keys deploy --env preview. On your machine, for a site on Cloudflare, run the build with ROUTINGCAT_ENV=production instead: see Deploy from your machine. Otherwise check ROUTINGCAT_DEPLOY_KEY, or that the Vercel project is still bound to your site. |
| The Vercel project is bound to another site | Make site in routingcat.config.ts the project’s site. |
No routingcat.config.ts |
Run npx routingcat init. |
| A local build | Nothing, for a build that tries your pages on your machine. To deploy production to Cloudflare from your machine, run the build with ROUTINGCAT_ENV=production: see Deploy from your machine. |
Once a production build registers, its deployment registers at its first request. If none has 10 minutes later, open your site; if it still doesn’t register, check RoutingCat’s entry on your host:
- on Vercel,
middleware.tsexportsmiddleware(…)fromroutingcat/vercel; - on Workers, the Wrangler configuration’s
mainexportsworker(…)fromroutingcat/cloudflare, and keeps the version metadata binding,CF_VERSION_METADATA, without which each isolate registers on its own; - on Pages,
nuxt.configkeepsroutingcat/nuxtin its modules, whose build wraps the Worker that Pages runs.
Pages show no decisions
A decision is RoutingCat’s choice of a visitor’s variants on a routed page. doctor checks each page’s first one.
- The middleware or Worker didn’t run (it did not run). On Vercel,
middleware.tsmust exportmiddleware(…)fromroutingcat/vercel, keep RoutingCat’s matcher entries, and run on Node.js, asruntime: 'nodejs'in itsconfigdeclares (another runtime). On Workers, the Wrangler configuration’smainmust exportworker(…)fromroutingcat/cloudflare, andrun_worker_firstkeep RoutingCat’s patterns. On Pages, the build must use Nitro’scloudflare_pagespreset, withroutingcat/nuxtinnuxt.config’s modules. - The page isn’t live yet. Only a production deploy makes a page live, and on Vercel, for about 2 minutes after its first one, the page shows its defaults and records arrivals only.
- Routing is off. The build log’s receipt says so;
npx routingcat set routing=onturns it back on. When your plan turned it off, past Hobby’s visitors a second month running or at Pro’s cap, upgrade or raise the cap instead. - Routing waits for a card. On Hobby, it starts once the site’s owner has a card on file: see Routing waits for a card.
- A slot has one variant. Nothing is routed there until you add a second.
- A cache in front of RoutingCat serves the page (a cache in front). Stop caching routed pages’ HTML there: Run it in production.
- The middleware or Worker lost access (no records). Redeploy, which mints a fresh credential. On Cloudflare’s Free plan, a Worker also stops past its daily requests: RoutingCat needs Workers Paid.
- On Cloudflare, the page is served over plain HTTP (plain HTTP). RoutingCat leaves those views undecided: in Cloudflare, your domain → SSL/TLS → Edge Certificates → turn on “Always Use HTTPS”.
- Decisions come, but no render beacon. Keep
routingcat/nuxtinnuxt.config’s modules, whose plugin sends it. In your own browser, an ad blocker also stops it.
A routed page shows its defaults
RoutingCat decides, but the visitors routed to the page see its defaults:
- The page has no head snippet (no head snippet). Keep
routingcat/nuxtinnuxt.config’s modules, which writes it into every page’s<head>. - The page’s answer has no RoutingCat entry in
Server-Timing(no entry). A header rule or a proxy in front of your site removes it: keepServer-Timingon your pages. - Your Content Security Policy blocks the snippet or its style rules (what the policy blocks). See Run it in production.
- The browser can’t show a selection. Safari before 16.4, Chrome before 105 and Firefox before 121 get the defaults, and their visits stay out of results.
- In
nuxt dev, a page opened over plain HTTP at another address thanlocalhostshows its defaults, and?rc=forces only the variants declared whennuxt devstarted: restart it after you add one.
A slot breaks the page’s layout
- Vue reports a hydration mismatch, or a slot moves out of its paragraph. A slot inside a sentence needs
tag="span": adivcloses the open<p>. See Build a page. - A slot leaves its table or list. The browser moves a slot placed directly inside a table, a list or a
select: put it inside a cell or an item, or around the whole table or list. - Slots render as boxes. Your Content Security Policy refuses their
style="display:contents"(the slot markup’s style): add'unsafe-hashes'and the hashdoctornames tostyle-src-attr. - Several variants of a slot show at once. A CSS rule that sets
displayon the slot’s own elements, such as.hero > div { display: flex }, overrides what hides them: style your own elements inside the variants instead. - A slot renders nothing. Its
slotnames a slot the page doesn’t declare: in development, Vue’s warning names it. Declare the slot, or correct its name. nuxt buildstops on a declaration, or undernuxt devthe page that imports it fails. Nuxt’s variants are made of fields, and a variant declared as a component fails its declaration: give it fields, and render them in the slot’s template.
Routing waits for a card
On Hobby, routing starts once the site’s owner has a card on file, and nothing is charged. Until then, every visitor sees your defaults, and:
- a deploy that routes a page logs
routingcat NOT LIVE routing waits for a card on file: add one at …, nothing charged; doctorshowscard on filewaiting, with the link;npx routingcat set routing=onis refused, with the way out.
The owner adds a card in the dashboard’s Settings → Billing, with nothing charged, or starts Pro’s 14-day trial. Routing starts within about 2 minutes, with no new deploy. A member asks the owner. See Add a card to start routing.
A variant never shows
- Your browser keeps its variants for the whole visit, 7 days by default, and may be in the control, the 10% who always see the defaults. To see a variant, add
?rc=hero:learnsinnuxt dev, or use a preview link in production: Change, preview and deploy. - It’s paused or retired.
npx routingcat show home/hero:learnssays so, andnpx routingcat resume home/hero:learnsbrings it back. - The plan holds it. On Hobby, past the 10 live variants across the owner’s sites, a variant is registered and not served: see A variant is held.
- It’s new, or routing favors others. A new variant starts on a limited share, and routing serves most what is likely best, though the random slice shows every variant now and then.
showgives its share. - It was added during a visit. A slot added mid-visit shows its default to that visitor until their next visit.
- Its build failed after registering (a build that failed after registering), as when a Cloudflare deploy fails after its build. Visitors keep the previous version: fix the deploy and redeploy, or revert the page.
- Browsers report it shown less often than the slot’s other variants (for a smaller share of the page loads it was picked for). Check that it renders as fast as they do.
A variant is held
On Hobby, the sites you own serve at most 10 live variants together, the slots’ defaults not counted. A deploy or an act past them still succeeds, and each variant past the 10th is held: registered, and not served. The build log names it after its first line, status reads held on its row, and the dashboard marks it “Held by the plan”.
To put it live, either:
- upgrade to Pro in Settings → Billing, for every site you own, which allows up to 100 live variants a page;
- or retire or pause a live variant on any of your sites, which frees room.
Held variants then go live in the order they were declared, within about 2 minutes, with no new deploy. See Go past Hobby’s 10 live variants.
Conversions don’t join
A routingcat.track or routingcat.identify call in your server code that sends nothing says why in your server’s logs, on a line starting routingcat:: a call without its event, a track without an id, a build that registered nothing, or a request RoutingCat’s module didn’t see.
Track conversions explains the tracking line of status. The messages behind it:
- Conversions with no key: from a request with no visitor cookie, such as a webhook’s, pass
visitorId, oraccountId. - Accounts never linked: call
routingcat.identifyat sign-up and at every sign-in. - Identify without a visitor id: often a callback posted from another site, which carries no cookie. Identify at the next page load instead.
- The preview server key used in production: use the production key.
- Events under an undeclared name: declare the name in
routingcat.config.tsif it matters. - No event yet, a silence or a drop: check that
routingcat.trackis still called, and after a drop, record a tracking gap. - Conversions with no routed visit need nothing: they count once their visitors see a routed page.
The control’s share is off
status raises the control check when the control’s share of new visitors departs from its setting, which can bias the measured gain. Check that page for a cache in front of RoutingCat, or for a matcher of the middleware or Worker that skips it for some visitors.
A preview link fails
| What happens | Do this |
|---|---|
preview says the production deployment doesn’t carry the version |
Deploy it to production first. |
preview says no deployment serving it has registered yet |
Right after a deploy, open your site, then run preview again. If the build failed after registering, deploy again. |
preview says the page has no path to preview |
Pass one: --path /pricing. |
preview refuses variants of several pages, or several of one slot |
Preview one page at a time, one variant per slot. |
| The page opens without the preview badge | The link is valid for one hour: make a new one. |
| The badge names another variant | The badge reads what the page shows. Right after a deploy, a new server instance can serve the page before it knows the variant: reload a few seconds later. |
To end a preview, follow the badge’s link.
Run commands in a sandbox
Coding agents often run commands in a sandbox, as Work with your coding agent explains:
- No network, exit code 4: allow network access for the command, or ask the person to run it.
- The sandbox can’t keep a sign-in, exit code 3: run
npx routingcat loginin your own terminal, then the command again. - A sign-in link is waiting, exit code 3: give the link to the person, and once they approve, run the same command again.
- A production build on the machine waits for the person, exit code 3: show them the plan it printed, with any warning beside it, and once they agree, run the build again with
ROUTINGCAT_CONFIRMset to its digest, besideROUTINGCAT_ENV=production. doctor --waitruns up to 5 minutes: give it a 6-minute timeout, or run it in the background.
Read doctor check by check
npx routingcat doctor reads your project, your sign-in, your site’s records and your production pages, and changes nothing. Its request to your production page is recorded as an arrival, flagged as RoutingCat’s own and kept out of results. After a deploy, --wait waits up to 5 minutes, printing each check as it passes, and stops early when only a person can move the rest. On Vercel, once setup is done:
$ routingcat doctorlumen · production ✓ build registered ch_01m3yfrpt0ewpateq9yrq61xkb · objective signup · outcomes waitlist 14:19:52 ✓ deployment registered dp_01m3yfn1m0exhr06xh9d10mvbr · commit 2528229 · routingcat 1.0.0 14:21:10 ✓ runtime Node.js 22.18.0, as dp_01m3yfn1m0exhr06xh9d10mvbr reports it ✓ middleware on / rc_vid on lumen.so, 400 days 14:40:00 ✓ /_rc reaches the collector 14:40:00 ✓ head snippet 1 page carries it ✓ policy lets RoutingCat run, apply and record ✓ records ingest.routingcat.com accepts them from https://lumen.so ✓ first arrival arrivals in the pulse, last 14:39:18 14:39:18 ✓ signup 2 events 14:33:59 · server · identify 14:32:05 14:33:59 ✓ card on file Visa •••• 4242 · AGENTS.md has no RoutingCat note, so coding agents may skip the skill unless a request matches it; if the person wants the note, run npx routingcat init --agents-md11 of 11 passed (exit 0)On Workers, Worker version and Worker on / take the place of runtime and middleware on /. Here a page is routed, so selection and the page’s own checks join the others, and the site is on Pro, which has no card on file check:
$ routingcat doctorlumen · production ✓ build registered ch_01m4ty5160eh9snxb60wje6p25 · objective signup · outcomes waitlist 10-13 15:30:00 ✓ deployment registered dp_01m4ty1c00fv89n1z02vhnd2s2 · commit 4943e25 · routingcat 1.0.0 10-13 15:31:30 ✓ Worker version b2c3d4e5-0000-4000-8000-000000000001, which dp_01m4ty1c00fv89n1z02vhnd2s2's isolates register under ✓ Worker on / rc_vid on lumen.so, 400 days 10:05:00 ✓ /_rc reaches the collector 10:05:00 ✓ head snippet 1 page carries it ✓ selection the rc entry on 1 routed page ✓ policy lets RoutingCat run, apply and record ✓ records ingest.routingcat.com accepts them from https://lumen.so ✓ first arrival arrivals in the pulse, last 10:03:40 10:03:40 ✓ signup 7 events 09:53:07 · server · identify 09:53:07 09:53:07 ✓ home live pb_01m4ty5160fqj90qfzka9y5s0x 08-30 10:00:00 ✓ first decision home, last hour 67 10:03:40 ✓ first render beacon from a browser 10:03:42 · AGENTS.md has no RoutingCat note, so coding agents may skip the skill unless a request matches it; if the person wants the note, run npx routingcat init --agents-md14 of 14 passed (exit 0)On Pages, the same run has no Worker version line:
$ routingcat doctorlumen · production ✓ build registered ch_01m4ty5160eh9snxb60wje6p25 · objective signup · outcomes waitlist 10-13 15:30:00 ✓ deployment registered dp_01m4ty1c00fv89n1z02vhnd2s2 · commit 4943e25 · routingcat 1.0.0 10-13 15:31:30 ✓ Worker on / rc_vid on lumen.so, 400 days 10:05:00…13 of 13 passed (exit 0)A check that fails gives its message and what to fix. Here RoutingCat’s Worker ran on no page, and no page carries the head snippet. On Workers:
$ routingcat doctorlumen · production… ✗ Worker on / https://lumen.so/ answered without RoutingCat's visitor cookie, rc_vid, so the Worker did not run on /: visitors there get your defaults, and their visits are not recorded. The Worker's main must export worker(…) from routingcat/cloudflare, and run_worker_first must keep RoutingCat's patterns. (D82) ✓ /_rc reaches the collector 10:05:00 ✗ head snippet https://lumen.so/ carries no RoutingCat head snippet in its <head>, so visitors routed there see your defaults. Keep routingcat/nuxt in nuxt.config's modules, which writes it into every page's <head>. (D89)…11 of 13 passed · 2 failed (exit 1)On Pages, Worker on / names the Worker your Pages build wrote, and the preset that build needs:
$ routingcat doctorlumen · production… ✗ Worker on / https://lumen.so/ answered without RoutingCat's visitor cookie, rc_vid, so RoutingCat did not run on / in the Worker your Pages build wrote: visitors there get your defaults, and their visits are not recorded. Keep routingcat/nuxt in nuxt.config's modules, whose build wraps that Worker, and build with Nitro's cloudflare_pages preset. (D82)…10 of 12 passed · 2 failed (exit 1)| Check | Passes when | Otherwise |
|---|---|---|
| Your files, shown only when one fails | Nuxt 4.6 or later, your host’s Nitro preset wherever your app names one, init’s edits, the site’s public key, and an up-to-date skill; on Workers, no Workers Cache |
Nuxt, the preset, Workers Cache, an edit to make, the wrong public key, the skill |
| build registered | A production build registered, with the objective and outcomes it declares | Waits for a production deploy |
| deployment registered | The deployment serving that build registered at its first request | No deployment registered after 10 minutes |
| runtime, on Vercel | The middleware runs on Node.js, as its deployment reports | Another runtime |
| Worker version, on Workers | The deployment registered under its Worker’s version | No version: the version metadata binding is missing |
| middleware on /, or Worker on / on Cloudflare | Your production page set rc_vid |
The middleware or Worker did not run, or a cache in front answered. If a firewall challenges the check’s request, doctor reads the pulse instead. On Cloudflare, it waits for your production address until routingcat.config.ts names it as origin and a production build has registered: Set up RoutingCat says how. |
| /_rc | Your site’s /_rc path reaches RoutingCat |
/_rc is missing |
| head snippet | Each page doctor reads carries RoutingCat’s head snippet |
No head snippet |
| selection, once a page is routed | Each routed page’s answer carries the variants picked for its visitor | No entry in Server-Timing, the middleware or Worker did not run there, a cache in front, or a build made for development on a person’s machine; waits within about 2 minutes of the deploy |
| policy | Your Content Security Policy, if any, lets RoutingCat’s snippet run, its rules apply and its records leave | What the policy blocks |
| Cloudflare’s features, shown only when one fails | Rocket Loader leaves RoutingCat’s snippet alone, and on Workers or Pages, your domain redirects plain HTTP to HTTPS | Rocket Loader, or plain HTTP |
| records | RoutingCat accepts records from your production address | The address isn’t one of the site’s domains |
| first arrival | The pulse shows an arrival, often the check’s own request | Waits about a minute |
| The objective, by its name | A first conversion arrived | No event yet: waits for the first real conversion, a visitor’s or your own, and nothing else waits on it |
| card on file, on Hobby | The site’s owner has a card on file, shown by its brand and last 4 digits | Waits for the owner to add one, at the link it gives: routing waits for a card |
| page live, first decision, first render beacon | Each routed page is live, decided, and shown in a browser | Waits for a visit; no render beacon, or a cache in front |
| server key, location headers, shown only when one fails | The preview key used in production, Cloudflare in front of Vercel without its location headers |
Lines marked · are information and need nothing: conversions with no routed visit yet, an undeclared name, and, run in your project, whether AGENTS.md has the note for coding agents, which is your choice.
doctor exits with 0 when every check passed, 1 when one failed, 3 when one is still waiting, and 4 when the network blocks it.