Run it in production
What RoutingCat adds to your pages, what visitors see while their browser decides, what visitors and records go through when RoutingCat can’t be reached, how caches and policies work with it, and how to upgrade, pause or remove RoutingCat.
Plan for its cost on each page
- The head partial puts RoutingCat’s core in each page’s HTML, about 35 KB compressed, inline, so that it decides in
<head>without waiting for a request. - Its post part, about 23 KB compressed, loads from
cdn.routingcat.comafter the page has been read, pinned by its version and its integrity hash. - A routed page’s routing data, at most 128 KB, is fetched from
cdn.routingcat.comat a visitor’s first view of the page, and kept in their browser for a day. Later views decide from it at once, and refresh it after the page shows. - Every variant is in the HTML. Hidden variants add their markup to each page, and their images, all lazy, are never fetched while hidden.
- Nothing RoutingCat sends delays a page. The browser posts its records as the page shows, without waiting for an answer.
Expect a first view to wait
A visitor’s first view of a routed page, with no routing data stored yet, waits for it from cdn.routingcat.com:
- Only the page’s routed slots wait, hidden with their space kept; the rest of the page shows at once.
- The wait lasts at most 1,000 ms by default, which
wait_msinroutingcat.config.jsonsets, up to 10,000. Past it, the slots show their defaults, and keep them for the rest of the visit on that page. - A variant of another height than the default moves the content below it when the wait ends on it. Keeping a slot’s variants the same height avoids it.
Every later view decides at once, from the routing data the browser stored.
Know what the browser decides
- A browser that doesn’t run RoutingCat’s script, with JavaScript off or the script blocked, shows your defaults, and its visit is never recorded.
- A browser without the style rules RoutingCat uses, CSS
:has()and cascade layers, as Safari before 15.4, shows your defaults, and its visit stays out of the comparison. - The visitor id is written by script, as browser analytics’ ids are. Safari deletes it after 7 days without a visit, so one person can count as several visitors over a month, in your results and in the visitors your plan counts. The server helpers set it from your server instead, for 400 days.
- A visit with no referrer counts as
unknowninsources, unless the fetch-site helper tells it from a typed or bookmarked visit. - Each decision is replayed. RoutingCat replays every decision a browser makes from what it recorded, and leaves one that doesn’t replay out of every result;
statuswarns past 1% of them. In the command’s JSON, results that rest on decisions browsers made carryverification: unverified: replay shows that each follows from its inputs, never that a person saw it. - The routing data is public. A determined visitor could pick their variant, which matters only for a variant your server honors, such as a price: see Weigh a variant your server honors.
Expect changes within about 2 minutes
- Pushes and acts reach RoutingCat’s CDN within about 2 minutes. A visitor’s browser decides each page from the routing data it stored, and fetches fresher data after the page shows when its copy is more than a minute old. So a returning visitor’s first page after an act can still show a paused or retired variant, and their next page follows the act.
- Turning routing off also reaches open pages. A page whose routing data is more than 5 minutes old checks the site’s routing as it shows, and switches to its defaults when routing is off.
- A page’s first production push serves the page’s defaults until its routing data is published, within about 2 minutes.
Know what happens when RoutingCat is unreachable
Visitors always get a page.
| What can’t reach RoutingCat | Pages | Records |
|---|---|---|
| A visitor’s browser, for routing data | A first view waits, then shows the defaults; a view with stored routing data decides as usual, for up to a day | Unaffected, unless ingestion is unreachable too |
| A visitor’s browser, for its records | Unaffected | The browser keeps what it couldn’t send, and sends it at the visitor’s next page; a decision that arrives that late counts for delivery only, outside the comparison. Browser events are not sent again |
| Your server, for the events API | Unaffected | Your backend gets an error, and can retry safely, since each event carries its id |
| A push | It exits with code 4 and registers nothing: run it again | |
| The command | It exits with code 4: run it again later. |
Pages whose head partial names templates no push registered show your defaults and record no decision, from the deploy until about 2 minutes after the push that registers them. Run
push --checkbefore every deploy andpushright after it, then read the push’s receipt, or runnpx routingcat doctor, which names such a page, and push again if it didn’t register.
Keep caches, policies and scripts from hiding decisions
- Caching your pages’ HTML is fine. A page carries every variant and no visitor’s decision, so a CDN or a page cache can share it; each visitor’s browser decides. The server helpers don’t run on a cached page.
- Keep RoutingCat’s core first. Cloudflare’s Rocket Loader would run it after the page shows: keep the
data-cfasync="false"the partial writes on its tag, or turn Rocket Loader off for those paths. Cloudflare’s Zaraz, auto-injected, puts its script before the partial: turn its Auto-inject off on those paths, and load it after the partial.doctornames both. - Let your Content Security Policy run RoutingCat. It must allow RoutingCat’s inline core, best by the page’s nonce: give the head partial’s script tags the nonce your template gives your own scripts, and
pushkeeps it each time it writes the partial again. Otherwise, add the core’s hash, whichdoctornames, toscript-src: an upgrade ofroutingcatcan change it. The policy must also allowhttps://cdn.routingcat.cominscript-srcandconnect-src,https://ingest.routingcat.cominconnect-src, RoutingCat’s inline style rules, by the core’s nonce, which they carry, or with'unsafe-inline', and the slot markup’sstyle="display:contents", by'unsafe-hashes'with its hash instyle-src-attr.doctorreads your policy and names each source to add. Under a policy that allows inline styles only by hash, every visitor sees your defaults.
Upgrade
- Set the new version as
routingcatinroutingcat.config.json, and in your CI’s steps where they name it. - Run
npx routingcat init, which now runs that version and brings the skill level with it, unless the skill was edited here, which it keeps, thennpx routingcat push, which writes the partial with that version’s core and post part. Commit what they change. Where your Content Security Policy allows the core by its hash rather than by nonce, add the new core’s hash, whichdoctornames. - Deploy, and register the deploy as usual. Visitors get the new code only with your deploy.
doctor reports a newer core or post part than the one your pages run. Learning, settings and acts never need an upgrade: they reach the version you deployed within about 2 minutes.
Pause or remove RoutingCat
- Turn routing off for the whole site with
npx routingcat set routing=off. Every page shows its defaults, and visits and conversions are still recorded.npx routingcat set routing=onresumes from what routing had learned. See Routing settings. - Remove it from your code in one change, then deploy:
- in each template, each slot’s markup, replaced by its default’s content;
- the partial’s include in each layout, and the partial itself;
- the page’s
window.routingcat.track()andwindow.routingcat.identify()calls,window.routingcat.routingcatPropertiesin your PostHoginit, and your servers’ calls to the events API; - the server helpers and the line that runs them, if you added them, and on Laravel, the line that leaves
rc_vidout of cookie encryption; routingcat.config.json, thepushsteps in your CI or your host’s deploy script, and the skill’s folders,.agents/skills/routingcat/and.claude/skills/routingcat/;- the note for coding agents in
AGENTS.mdandCLAUDE.md, from<!-- routingcat -->to<!-- /routingcat -->, andAGENTS.mditself if the note is all it holds.
- Archive the site. Archiving revokes the site’s keys, stops collection, and deletes the site’s records within 90 days, unless you ask otherwise. The site’s owner asks RoutingCat to archive it. To have the records deleted sooner, ask for their deletion.