RoutingCat User Guide
How to earn more from the visitors you already have.
RoutingCat shows each visitor the version of your page likely to earn the most. This guide covers everything it does.
What you will be able to do
- Try an idea the day you have it.
- Go for profit, not for the most orders.
- Improve every part of a page at once.
- Fit the page to each visitor and each ad.
- Measure exactly what it added.
- Keep your site fast, up and private.
The example
SpellhomeSimulated store
Scented soy candles made to be gentle on dogs: one candle, two scents, four label designs. A Next.js store on Vercel, with Stripe for payments.
- Visits to the product page
- 100,000 a month, mostly from Meta and Google ads
- Today’s offer
- 1 candle for $51 plus $9 shipping · 2 for $94 · 4 for $136
- What an order costs
- $8 a candle, and $8 to ship plus $2 per extra candle
- What the page earns today
- 3 in 100 visitors buy · $56 profit an order · $1.68 a visit
Spellhome is a real store. Its numbers here are simulated, so that every figure in this guide comes from one consistent run. Terminal output is shortened to the lines that matter.
Understand it3 min
What RoutingCat does
Several versions of a page live at once, each visitor sent to the one likely to earn the most, and the gain measured.
A page that sells is a stack of decisions. Open Spellhome’s product page on a phone and count them: which photo comes first, whether one candle is selected or two, that a candle costs $51 and not $57, that the old price is struck through, that shipping is $9 on one candle and free on two, that the cart suggests nothing more.

- First photo
The jar on white, or the candle at home?
- Anchor
$68 struck through, and how the saving is shown.
- Offer selected
One candle, with the pair a tap away.
- Price
$51, and not $57.
- Shipping
$9 on one candle, free on two.
- Cart
A cart that suggests nothing.
Each was a reasonable call, and none was checked. A pricing page has the same stack: the plan shown first, monthly or yearly by default, the headline, the proof under it. Checking one of these properly ties up the page for weeks, so most sites check a few a year and guess the rest.
What a guess costs
The arithmetic is short: visits, times what a visit is worth. Spellhome’s page takes 100,000 visits a month. Three visitors in a hundred buy, and an order leaves $56 after the candles and the shipping, so a visit is worth $1.68.
- Visits a month
- 100,000
- Visitors who buy
- 3.0%
- Profit an order
- $56
- A visit is worth
- $1.68
- The page earns, a month
- $168,000
- +1¢ a visit
- +$1,000 a month
- +20¢ a visit
- +$20,000 a month
At that traffic, one more cent per visit is $1,000 a month, and all of it is kept, because the ad bill does not move when the page gets better. If the right offer is worth 20 cents a visit more than the one on the page, the guess costs $20,000 a month, and no dashboard says so.
Slots, variants, the default
RoutingCat replaces the guess with a system. It has four words, and you will meet them in every chapter:
- A page is a page of your site that RoutingCat routes: the product page, the pricing page, the home page.
- A slot is a place on that page that can change: the headline, the first photo, the price list, the order of the sections, or the whole page.
- A variant is one alternative for a slot. Anything your code can render can be one.
- The default is the variant you have today. Every slot keeps one, and it is what the page falls back to.
You declare slots and variants in your own code, with your coding agent, and they go live with your normal deploy.
What happens to each visitor
From then on, RoutingCat picks each visitor’s variants before the page shows:
- Every variant stays live. There is no test to start, stop or schedule.
- Each visitor gets the version most likely to earn the most. A model estimates what each variant is worth from the conversions so far, and each variant gets visits in proportion to its chance of being the best. A weak variant loses its visitors on its own. An uncertain one keeps being tried.
- A visitor keeps what they saw: the same variants for their whole visit, seven days by default.
- One visitor in ten keeps today’s page. This control is chosen at random, and always sees the defaults.
9 in 10 visitors
routed between your variants
1 in 10
the control: keeps today’s page
Because the control is random, the difference between it and everyone else is a measurement, not an estimate. RoutingCat calls it the measured gain. It is the number you can repeat to a partner or a board.
What is left to you
The machine learning stays in the background. You never choose a sample size, a stopping rule or a significance level. Your part is the part that needs judgment:
- Say what a conversion is, and what it is worth to you: an order’s profit, a plan’s lifetime value. You do it once (chapter 4).
- Decide what to try. Your agent builds it.
- Read what came back, with how sure it is.
- Retire what lost, keep what works, and try the next idea.
Where it applies
Any page where visitors decide: a product page, a pricing page, the landing page your ads point to, a sign-up page, a home page. RoutingCat needs a conversion it can count, such as a purchase, a sign-up or a booked demo, and enough of them to learn from. Chapter 6 says how many.
This site’s own home page runs it: its main button, its install box, the layout of its pricing and the order of its sections are all slots.
Read more: How RoutingCat works · Pages, variants and versions
4 min
Routing and A/B testing
A test is a project that ends in a verdict. Routing keeps every version live, moves visitors as it learns, and never needs calling.
If you have run A/B tests, RoutingCat can sound like a better testing tool. It is a different way to work, and the difference shows at every step of an idea’s life.
A test
Routing
Build
A visual editor, or a developer’s time
A request to your agent: anything your code renders
Launch
Needs the page to itself
Joins the other slots
Wait
A fixed split: the loser keeps its share
Traffic moves with the conversions
Decide
Someone sizes it, watches it, calls it
Nothing to call: a loser fades
Ship
The winner is rebuilt in code
Already in your code
After
One answer for everyone, no longer measured
An answer per audience, still measured
An idea costs a sentence, not a project
A test is a small project. Someone builds the alternative in a visual editor, or waits for a developer. Someone works out how many visitors it needs, launches it, watches it, and decides when to stop. If it wins, someone rebuilds it properly in the site’s code. That cost is why most teams run a handful of tests a year, and why they keep them safe.
With RoutingCat, a variant is a few lines in your repository, written by your coding agent. It goes live with your next deploy and joins the variants already there. Nothing has to be watched, because a variant that does worse loses its visitors on its own. Nothing has to be called, because there is no end date.
This matters more than any statistic. Most ideas do not win, whoever has them. What separates a page that keeps improving from one that does not is how many ideas reach visitors, and how bold they are. When trying one costs a sentence and a mistake corrects itself, you try many more.
Anything your code can render
A visual editor changes what it can reach on the finished page: a headline, a colour, a button. Past that you need a developer, and the test waits.
A RoutingCat variant is code in your own site, so it can be anything your site can do: another price list, another checkout step, a reordered page, a whole new page. Your agent works out how to build it on your stack, and you review it like any other change. On Vercel and Cloudflare it is rendered by your own server, so the page arrives already in its variant, with no flicker. Chapter 5 shows the shapes a variant takes.
It earns while it learns
A test splits visitors evenly and holds that split until it ends. By design, the worst variant keeps its full share the whole time. Routing starts the same way, then moves visitors toward what is working.
A test
- Earned by week 4
- +$5,287
- By week 8, called at 4
- +$34,888
Routing
- Earned by week 4
- +$15,581
- By week 8
- +$38,066
At Spellhome, routing had three visitors in four on the best of three offers by week four, and had earned about three times what the even split had. To be fair to the test: stopped at the right moment, with the best offer then shown to everyone, it nearly catches up by week eight. But someone had to pick that moment, and routing was earning from the second week.
Routing does not need to be sure to be useful. A test pays nothing until it is finished. Routing pays in proportion to what it knows so far.
Every question at once
A test needs the page to itself, so questions wait in line. Seven questions at four weeks each is more than half a year. Slots do not wait: every slot of a page learns from the same visits, at the same time.
One test at a time
+$60,124 by week 12
Routing
+$126,273 by week 12
Chapter 6 covers how many slots a page can carry.
An answer for each kind of visitor
A test ends with one answer for everyone. But visitors differ, and a variant that is average overall can be the best by far for some of them. At Spellhome, an offer built around the full set of four candles was unremarkable across all visitors, +12% [−5, +30], and the strongest offer the store had for homeowners, a quarter of its traffic. A test on everyone would have dropped it.
Routing can learn a leader for each kind of visitor, so its ceiling is higher by construction: the best version for each audience is worth at least as much as the best single version. Chapters 8 and 9 show how.
What a test still does best
A fixed split gives the plainest comparison between two versions, and each visitor keeps the same one on every visit. RoutingCat has it built in: give a slot a fixed share of 100% and it is a classic A/B test. Use it for a single question where you want the cleanest read, or where a visitor must keep what they were shown for weeks. Chapter 7 says when that matters for a price.
Worth knowing
- Routing does not create evidence. A small difference still takes many conversions to see. What changes is that you do not have to wait for certainty before you benefit.
- The gain is still measured the classic way. One visitor in ten stays on your defaults, at random. That comparison is a randomized experiment, and it never stops running.
Read more: How routing learns · Routing settings
Set it up3 min
Install, and work with your agent
One command installs it in your repository. After that you work through your coding agent, the way you already do.
RoutingCat is a developer package. You could write every line of it by hand, and the docs say how. Most people never do: they install it, and from then on their coding agent does the RoutingCat part.
Install
For example, you ask
Install RoutingCat in this repository: run npx routingcat init, then finish the setup with the RoutingCat skill.
Your agent
Bashnpx routingcat init
✓ Next.js 16.4.3, App Router · pnpm✓ Vercel project spellhome · signed in to RoutingCatDetected for outcomes: Stripe (payments)1 create the site spellhome2 pnpm add routingcat3 next.config.ts wrap the export in withRoutingCat()4 proxy.ts new: RoutingCat picks each visitor's variants here5 app/layout.tsx add <RoutingCat /> to <body>6 routingcat.config.ts new: the site and its public key7 the skill .agents/skills/routingcat/Nothing reaches visitors until you deploy.
A handful of small edits, all visible in git diff:
- The proxy is where each visitor’s variants are picked: on your own server, before the page renders.
- The config names your site, and soon what counts as a conversion (chapter 4).
- The skill is what makes your agent an expert. It teaches it how to declare a variant on your stack, how to read every number, and what to retire or try next. It works with Claude Code, Codex, Cursor, Gemini CLI and GitHub Copilot.
This guide’s code is a Next.js site on Vercel. RoutingCat also runs on Nuxt and Astro, on static and server-rendered sites on Vercel or behind Cloudflare, and through a script on any other host. Setup differs; the rest of the guide holds. Shopify themes are coming soon, with early access on request.
Work the way you already do
People work with coding agents in different ways, and each of them works here.
Some say exactly what they want.
For example, you ask
On the product page, try each offer as the one selected when the page loads: one candle as today, the pair, and the set. Call the slot “lead”.
Your agent
Writeroutingcat/pdp.ts
Editsrc/modules/offer/OfferStack.tsx
Bashnpx routingcat plan
pdp new page · routes /products/lavender, /products/vanillalead new slot · default single+ single the default, which the control always sees+ pair scorecard against single+ set scorecard against singleIn production this goes LIVE, with routing within about 2 minutes:the control keeps seeing the defaultsthe rest are routed toward the most VPV (profit)valid push will accept this
Ready. The page still opens on one candle by default, and two new variants open it on the pair and on the set. Prices and everything else are unchanged. To see them before they ship, run the store locally and open /products/lavender?rc=lead:pair, then the same address with lead:set. Nothing reaches visitors until you deploy.
Some talk it through first. They describe a problem, such as “six orders in ten are a single candle, and that is our thinnest order”, and ask what could change it. The agent reads the page and the prices, proposes a few ideas with its reasons, says which ones need a decision from you, such as a price, and builds the ones you choose.
Some hand over the routine. Once a week they ask for a review, or schedule one where their agent can run on a schedule: what changed, what to retire, what to try next. The agent reads the results, acts within what it is allowed to do, and reports what it did and why.
Whichever way you work, the result is the same few lines in your repository, declaring a slot and its variants. That is why each chapter keeps the code at hand, folded under “In your code”, with one example of how you might ask for it. Your own words will do as well: the skill gives your agent RoutingCat’s vocabulary, so you do not need it.
What reaches visitors, and when
- A new variant goes live only through your own deploy, so it gets the same review as any other change. Before it,
npx routingcat planlists exactly what the deploy will put in front of visitors. - You can look first. In development, adding
?rc=lead:pairto the page’s address shows that variant. - A preview deploy changes nothing live.
- Stopping is faster than shipping. Pausing or retiring a variant takes effect within about two minutes, with no deploy (chapter 11).
What your agent may do alone
Your repository’s agent instructions (AGENTS.md, or CLAUDE.md) say what your agent may do without asking. The line most teams draw: it reads results freely, and you approve anything that changes what visitors see.
What it can work from
Your agent is not limited to RoutingCat’s numbers. Put last month’s ad sets, exported from Ads Manager, in the repository, and it will read them. If it can already query your orders in Stripe or your events in PostHog, it will use those too. None of them has to be connected to RoutingCat.
Read more: Set up RoutingCat · Work with your coding agent
3 min
Conversions and what they are worth
Say what counts as a conversion, then what one is worth to you. Routing then goes for what a visit earns, not for how many convert.
Routing learns to get more of something. Setup is where you say what: first which event is the conversion, then what one is worth.
The objective
The objective is the conversion routing optimizes: a purchase, a sign-up, a booked demo, a trial started. A conversion counts when it happens within a window after the visitor first saw the page, 24 hours by default.
In your coderoutingcat.config.ts
routingcat.config.ts
objective: 'signup',
outcomes: ['contact_sales', 'newsletter'],Outcomes are the other events you want to see beside it, such as a click on “Talk to us” or a newsletter sign-up. They are counted and reported for every variant, and never steer routing. The site has one objective, and a page can have its own.
What a conversion is worth
Left alone, every conversion counts the same. On a page whose variants can change what is bought, that is the wrong target. An order for four candles is worth twice an order for one. An annual plan is worth more than a monthly one. An order won with free shipping is worth less than the same order without it.
So you give the objective a value: your own definition of what a conversion leaves you. Routing then optimizes value per visit, written VPV: how many visitors convert, times what a conversion is worth.
- the pair: 3.3% × $66$2.19
- one candle: 3.1% × $58$1.80
- the set: 2.3% × $70$1.63
Counted over the first eight weeks, by the offer selected when the page loaded.
What it is for
A value can be whatever your business counts in:
- A store’s profit. What the customer paid, less what the goods and the shipping cost you. With real margins per product if you have them, an average margin if you do not.
- Revenue, where margins are even: the amount of the order.
- A plan’s lifetime value. A $19 monthly plan at an 80% gross margin, whose customers leave at 5% a month, is worth about $19 × 0.8 ÷ 0.05 = $304. If 40% of its trials pay, a trial is worth about $122.
- What a lead is worth. A fixed number for a booked demo, or a figure your own code computes for each one.
In your coderoutingcat.config.ts: each plan at its lifetime value
routingcat.config.ts: each plan at its lifetime value
objective: {
value: { purchase: { items: { 'stripe:price_monthly': 304, 'stripe:price_annual': 420 } } },
unit: 'USD',
name: 'lifetime value',
},In your coderoutingcat.config.ts · where your server confirms a paid order
Spellhome optimizes for profit, from one sentence:
For example, you ask
Count a purchase as our conversion, and optimize for profit: what the customer paid, minus $8 per candle, minus shipping at $8 plus $2 for each extra candle.
| Order | Paid | Candles | Shipping | Profit |
|---|---|---|---|---|
| 1 candle | $60 | −$8 | −$8 | $44 |
| 2 candles | $94 | −$16 | −$10 | $68 |
| 4 candles | $136 | −$32 | −$14 | $90 |
A candle costs $8. An order costs $8 to ship, plus $2 per extra candle. One candle sells at $51 plus $9 shipping.
routingcat.config.ts
export default config({
site: 'spellhome',
publicKey: 'pk_spellhome_8Hq2…',
objective: {
value: { purchase: { property: 'profit' } },
unit: 'USD',
name: 'profit',
cap: 300,
},
})where your server confirms a paid order
routingcat.track('purchase', {
id: order.id,
value: order.total,
currency: 'USD',
properties: { profit: order.total - 10 * order.candles - 6 },
})The value is declared once. Your server sends each order’s profit with the purchase, and the visitor’s id travels through checkout, so that each order finds the visit that led to it.
Where conversions come from
- Your own server, with
routingcat.track, as above. Each event carries anid, so one counted twice by a payment provider counts once. - A payment connection.
npx routingcat connect stripecredits Stripe’s payments to the visits that led to them, with no webhook code. Whop connects the same way. - Any other backend, over HTTP.
- Across devices.
routingcat.identify(user.id), called where an account is created and at each sign-in, links a person’s devices, so a purchase on a laptop still counts for the visit that started on a phone.
What you see
Results read in your own unit, and every difference splits in two:
Your agent
Bashnpx routingcat status pdp
pair favored VPV (profit) $1.96 [1.72, 2.20] · served 57%+17% [+2, +31] vs default+17% VPV (profit): +4% conversions, +12% per conversion
That last line says where a difference comes from: more buyers, or bigger baskets. Here it is mostly bigger baskets.
Worth knowing
- Give a value where variants can change what is bought: products, prices, plans, offers. Where they can only change how many visitors convert, as most headlines do, a plain objective learns faster.
- It is yours. RoutingCat takes the number you define, and never needs your cost sheet.
- An amount counts only from your server or a payment connection. A browser can report that a sale happened, never what it was worth.
- Leave out the ad spend. That money is gone before the page acts, whichever variant shows.
- Set a cap. No single visit counts for more than $300 at Spellhome, so one wholesale order cannot steer the page.
- Give it early. The measured gain in your unit starts from the deploy that declares the value. Changing the values later keeps what routing learned. Changing the events or the window starts the page’s learning again.
Read more: Optimize for what a visit is worth · Track conversions · Stripe · PostHog
Try things2 min
What a variant can be
A headline, an image, a section, the order of a page, the whole page: a variant is anything your code can render.
A slot is a place on a page that can change, and a variant is one alternative for it. Because variants are written in your own code, there is no list of what they can be. If your site can render it, it can be a variant.
A value: a headline, a price
An image
A section
The order of the sections
The whole page
What it is for
| To try | The variant holds |
|---|---|
| A headline, a line, a button’s label | A value. A headline and its subhead together, when the two must match |
| An image | The image, imported from your repository |
| A section, shown or not | The section, or nothing |
| The order of the sections | The order, as a list of names the page already knows |
| A layout, a redesign | A component of yours |
| A whole page | A component that renders the page |
| A price or a plan | Chapter 7 |
| A version for some visitors only | Chapter 9 |
A few of these, as they look on a real page:
nonedefaultno suggestion

complete-setcomplete the set

trimmer-addonadd a trimmer, $12

This site’s own home page tries the order of its sections, and its pricing in two layouts, each a component. Spellhome tries its long story page against a short one that opens on the dinner scene: one slot, whose two variants are whole pages.
In your coderoutingcat/pdp.ts · the product page
A value or an image is a field. The page reads it and renders it:
routingcat/pdp.ts
photo: slot({
default: 'jar',
variants: {
jar: variant({ fields: { photo: jar } }),
lifestyle: variant({
fields: { photo: atHome },
description: 'The candle lit in a living room',
}),
},
}),the product page
const x = await routingcat.decide(pdp)
<x.photo>{(f) => <Image src={f.photo} alt="Spellhome candle" />}</x.photo>An order is a field too, holding the names of sections the page already has:
sections: slot({
default: 'current',
variants: {
current: variant({ fields: { order: ['plans', 'contact', 'waitlist'] } }),
waitlist: variant({
fields: { order: ['waitlist', 'plans', 'contact'] },
description: 'The waitlist before the plans',
}),
},
}),A layout or a whole page is a component, rendered as <x.pricing />:
pricing: slot({
default: 'current',
variants: {
current: variant({ component: Pricing, content: { plans } }),
compact: variant({
component: PricingCompact,
content: { plans },
description: 'Plans as a price list',
}),
},
}),For example, you ask
On the pricing page, try the plans as a compact price list, next to today’s layout.
A fix, or a new idea
How you change a variant decides what it is compared with:
- A fix or a polish keeps its key. The edit makes a new version,
@v2, which builds on what the variant already showed. It is compared with the version it replaced. - A new idea takes a new key. It goes live beside the others and is compared with the slot’s default, at the same time and on the same traffic. That is the fair comparison.
- A refinement can run beside its original, under a new key that names its parent. It starts from part of what its parent showed.
Every version of every variant is kept, with its results and the commit that made it. Your agent starts each session knowing what was tried and what it did.
See it before it ships
- In development, add
?rc=photo:lifestyleto the page’s address to see that variant.?rc=offreturns to the defaults. - In production,
npx routingcat preview pdp/photo:lifestyleprints a signed link, valid for one hour, that shows the variant without counting in your results.
Worth knowing
- A new variant starts on a small share. It begins from what the slot’s other variants taught routing, never from a guess that it is better, and earns more visits with evidence of its own.
- Keep what you try inside the variant. A change to a shared stylesheet, or to content fetched when the page renders, makes no version, so nothing is compared.
- Variants are public copy on a live business. Facts, numbers, prices and guarantees must be true and yours. Your agent reuses what your site already says, and asks you for anything new.
- Deleting a variant from your code retires it at the next deploy, and keeps its history.
Read more: Build a page · Ship changes · Pages, variants and versions
2 min
Many slots at once
Every slot of a page learns from the same visits, at the same time. Six questions take the time of one.
A test runs alone. Slots do not have to. A page can carry several, each with its own variants, and every visit teaches all of them at once. Nothing is split between them: a visitor gets one variant in each slot, and the model works out what each contributed.
lead
Which offer is selected
prices
The price list
savings
How the saving is shown
gift
A free wick trimmer
photo
The first photo
cart
What the cart offers
What it is for
- Asking several questions in the same month. The first photo, the price list, how the saving is shown, what the cart suggests: Spellhome ran six slots on one page.
- Finding out what matters. You rarely know in advance which part of a page moves the result. Nobody at Spellhome had ranked the photo first, and it made the largest single difference: the candle lit in a living room, in place of the jar on white,
+28% [+18, +37]. - Keeping ideas moving. A new slot does not wait for another to finish, and adding a variant to one slot does not restart the others.
jardefaultthe jar

lifestyleat home

handin a hand

fourfour designs

In your coderoutingcat/pdp.ts
A page’s slots sit side by side in one file. Each has its default and its variants:
routingcat/pdp.ts
export const pdp = page('pdp', {
routes: ['/products/lavender', '/products/vanilla'],
slots: {
lead: slot({ default: 'single', variants: { /* the offer selected */ } }),
photo: slot({ default: 'jar', variants: { /* the first photo */ } }),
savings: slot({ default: 'struck', variants: { /* how the saving is shown */ } }),
cart: slot({ default: 'none', variants: { /* what the cart suggests */ } }),
},
})For example, you ask
Add a slot for the first photo on the product page: the jar as today, the candle lit at home, in a hand, and the four designs together.
One rule: slots stay independent
Routing judges each slot’s variants on their own effect. It does not learn that one headline only works beside one photo. Two things follow:
- Every combination must make sense, because a visitor can get any of them: any photo with any price list.
- Pieces that must match go in one slot, as parts of one variant: a headline and its subhead, a price and the line that explains it.
What your traffic can tell apart
Evidence comes from conversions, not from visits. At a 3% conversion rate:
| To tell apart a difference of | Each variant needs about |
|---|---|
| 50% | 2,000 visits |
| 20% | 13,000 visits |
| 5% | 200,000 visits |
So try ideas, not wordings. A different photo, a different bundle, a different promise. Not two ways to say “free shipping”. As a guide, tens of conversions a week support one or two bold alternatives in a single slot. Hundreds a week support three to five per slot, in several slots.
When several variants are still inside each other’s ranges after months, that is an answer too: the choice does not matter much. At Spellhome, three ways of showing the saving never separated. Keep one, and spend the slot on a bolder idea.
Worth knowing
- A losing variant costs little while it is live. Routing has usually cut it to a few percent of visits before you look. Retiring it is housekeeping: it frees room for a new idea.
- A static page is built once per combination. Three slots of four variants make 64 pages. RoutingCat warns past 100 combinations and refuses past 1,000. A page rendered on the server has no such limit.
- The plan sets how many variants can be live: 10 across your sites on Hobby, the defaults not counted, and 100 a page on Pro.
Read more: Pages, variants and versions · Limits
7 min
Prices and offers
The price, the bundle, the anchor, the gift: where a page makes or loses the most. Route them on what a visit is worth, with one price for everyone.
The offer is where a page that sells makes or loses the most money, and where operators guess the most: what it costs, what is bundled, which option is already selected, how the saving is shown, what comes free. Each of these can be a slot.
What it is for
A few of the questions an offer raises, each one a slot with two to four variants:
- Which offer leads. One item selected when the page loads, or the bundle. The plan highlighted on a pricing page. Monthly or yearly shown first.
- The price list itself. A dearer single item that makes the bundle look better, shipping folded into the price, “buy 2, get 1 free”.
- How the saving is shown. In dollars, in percent, as a price per item, or not at all.
- What comes with it. A free gift, a guarantee, a shipping threshold, a trial length.
- What the cart suggests. An upgrade to the larger bundle, or an add-on.
singledefaultone candle selected

pairthe pair selected

setthe set selected

Opening the page on the pair instead of one candle was worth +16% [+5, +28] at Spellhome, with no change to any price. The set, opened the same way, sold about a quarter fewer orders and came out behind.
Judge an offer on what a visit is worth
A generous offer nearly always wins more orders. The question is whether it earns more. That is why offers are judged on value per visit, which chapter 4 sets up: how many visitors buy, times what an order leaves you.
todaydefault$51 plus shipping

single-57one candle at $57

shipping-in$58, shipping included

three-for-twobuy 2, get 1 free

Your agent
Bashnpx routingcat status pdp
prices default today · 4 live · personalization offtoday default VPV (profit) $1.96 [1.73, 2.20] · served 18%single-57 favored +5% [−9, +19] vs default · served 39%shipping-in learning −6% [−20, +8] vs default · served 25%−6% VPV (profit): +3% conversions, −9% per conversionthree-for-two learning −6% [−21, +9] vs default · served 19%−6% VPV (profit): +4% conversions, −10% per conversion
Both generous lists are easier to buy from, and neither earns its cost back so far. Their orders are worth about 10% less, and that part is already clear: free shipping gives up $2 on every single candle, and three for two sells three candles to people who would have bought four. Whether they win enough extra orders to make up for it isn’t known yet, and nothing suggests they do.
A tool that counts orders would have kept one of those two. “Buy 2, get 1 free” went on to lose clearly, −12% [−23, −2], and was retired. The dearer single candle ended at +7% [+1, +14]: some buyers who would have taken one candle took the pair, and it cost no orders that anyone could see. The single candle’s job, it turned out, was to sell the pair.
Prices are routed like everything else
A price is a trade-off in its plainest form: a higher one sells fewer and earns more on each. Value per visit weighs both sides in one figure, so once a page has a value, a price is a variant like any other. The prices you want to try go live together, visitors move toward the one that earns the most, and a price that loses money loses its visitors without anyone watching it.
That matters more for prices than for anything else on a page, because there are so many candidates: the price point, the bundle, what one item costs beside the bundle, shipping in or out, the threshold for a gift. Tested one pair at a time, each losing price holds half your visitors until someone calls the test, and the list takes a year. Routed, you put in the candidates you believe in and read the page once a week. Spellhome’s four price lists above ran exactly that way.
The same works on a pricing page:
In your coderoutingcat/pricing.ts
routingcat/pricing.ts
pro: slot({
default: 'p19',
personalization: 'off',
variants: {
p19: variant({ fields: { price: 19, priceId: 'price_pro_19' } }),
p24: variant({ fields: { price: 24, priceId: 'price_pro_24' } }),
p29: variant({ fields: { price: 29, priceId: 'price_pro_29' } }),
},
}),For example, you ask
Try $24 and $29 for Pro on the pricing page, beside today’s $19. Count each price at its own lifetime value, and keep personalization off.
Three things make a routed price sound:
- A value that counts what the price changes. For a store, profit, so that a discount or free shipping shows as what it costs. For a subscription, each price at its own lifetime value, worked out as in chapter 4: $304 for the $19 plan, $384 at $24 and $464 at $29, if customers stay as long at each price. If the dearer plan loses them sooner, its figure should say so.
- Personalization off, so that the slot finds one price for all visitors. The next section says why.
- A checkout that charges what the page showed. Carry the price in the variant, as the Stripe price id above, and let the checkout read it from there.
Never route a price on a page that only counts conversions. The cheapest price converts best, so routing would drift to it. Give the page a value first.
One price for everyone, unless you decide otherwise
Left on, a slot learns for whom from the visit itself: the device, the hour, a first or a repeat visit. On a price, that is the wrong thing to learn. A price that follows a visitor’s phone or the time of day is one you could not explain to a customer, and in some places it carries legal weight. RoutingCat recommends against it. So a price slot runs with personalization off: routing still finds the price that earns the most, and it is the same for everyone.
What you can do is decide. A price meant for the visitors of one campaign or one source is ordinary commerce: an introductory price for the people a launch campaign brings, a lower first order for the visitors a partner sends, a price list for one country. That is a rule, only on the price (chapter 9), and rules hold with personalization off:
In your coderoutingcat/pdp.ts
routingcat/pdp.ts
prices: slot({
default: 'today',
personalization: 'off',
variants: {
today: variant({ fields: { list: 'today' } }),
'single-57': variant({ fields: { list: 'single-57' } }),
'pair-84': variant({ fields: { list: 'pair-84' }, only: { avatar: 'first_dog' } }),
},
}),For example, you ask
Try the pair at $84 instead of $94, only for visitors from our dog_ ad sets.
Visitors from those ad sets are then routed between that price and the others, on profit, and nobody else ever sees it. Its results read “for this audience”: whether first-dog owners are worth more to Spellhome at $84 or at $94.
One limit: a price that an ad has promised is not a variant. Every visitor from that ad must get it, and routing would send some of them to the other prices, and one in ten to the control. Honor a promised price in the site itself, with a coupon or a page of its own. Route a price while it is still a question.
When a fixed split is the better tool
A slot can also take a fixed share. At 100% it is a classic A/B test: its visitors are split evenly, each keeps the same price on every visit, and the slot gets a measured comparison of its own.
It is one setting: fixedShare: 1 in code, or npx routingcat set pricing/pro fixed_share=100% on a live slot, without a deploy.
For example, you ask
Run the Pro prices as a clean A/B test instead: an even split, each visitor keeping their price.
Choose it when:
- A visitor must keep their price for weeks. Under routing, a visitor keeps the same price for the whole visit, seven days by default, and a later visit can show another. Where people come back many times before they buy, as a team does on a pricing page, a fixed split holds each of them to one price for as long as it runs.
- You want the plainest read on two prices, for a decision you will make once and announce. An even split gives both the same number of visitors. Routing leaves the trailing price with few, so its own figure stays less precise.
- The page has no value. With an even split, you can weigh orders against revenue yourself.
It costs what every test costs: the weaker price keeps half your visitors until you end it.
The parts around the price
struckdefaultdollars saved

percentpercent saved

eachper candle

plainno anchor

Removing the anchor was the worst idea Spellhome tried: −25% [−39, −12], retired after four weeks. The three other ways of showing the saving never separated.
A free wick trimmer with two candles or more did not pay for itself either. Each order with it was worth less, which was certain, since the gift cost $3. Nothing showed it winning more orders: −5% [−12, +2]. Offered in the cart for $12 instead, the same trimmer made each order worth about 4% more.
Worth knowing
- Show a price on one page. Pages are routed independently, so the same price shown on two pages could differ between them.
- Count what an offer costs you. A gift, free shipping or a discount belongs in the value of the order, or it will look better than it is.
- Where the browser picks the variant, as with the script integration, a determined visitor could pick the lower price, while most browse normally. Your agent says so before it builds one.
Read more: Optimize for what a visit is worth · Routing settings
Fit each visitor4 min
Personalization and audiences
Visitors differ. A slot can learn which variant works for each kind of visitor, from what RoutingCat reads in a visit and from what only you know.
One best version for everyone is the first thing routing finds. It is rarely the whole answer, because visitors are not alike. Someone on a phone, back for the third time, is not someone on a laptop who just clicked an ad. Personalization is routing learning the difference, slot by slot.
What RoutingCat knows about a visit
- Device
- mobile
- Traffic source
- paid
- First or repeat visit
- repeat
- Time of day
- evening
- Day
- weekend
- Ad network
- meta
Yours
- Ad set
- cat owners
- Landing page
- /gifts
RoutingCat describes each visit along a few dimensions, each with a short list of levels:
| Dimension | Levels |
|---|---|
device | mobile, desktop, other |
traffic_source | direct, search, social, paid, email, referral, ai, other |
visit | first, repeat |
time_of_day | morning, lunch, afternoon, evening, night, unknown |
day_type | weekday, weekend |
ad_network | meta, google, microsoft, tiktok, linkedin, pinterest, other, none |
It personalizes on the context of a visit, never on who the visitor is.
What it is for
A slot learns for whom by default, over the first five of those dimensions, with nothing to set up. Routing personalizes only where the evidence earns it, so most of the time you will simply find that:
- A shorter first screen leads on phones, while the long one keeps desktop.
- A first visit and a repeat visit want different things: proof and reviews for one, the fastest way to reorder for the other.
- Search visitors arrive with a question, social visitors were interrupted. The same headline rarely serves both.
- Evenings and weekends browse, weekday lunches buy. An offer with a deadline can lead at one time and trail at another.
None of these is a setting. They are what a slot finds on its own, and what the results show you by dimension.
Your own audiences
The dimensions that matter most are usually yours. You already sort visitors when you buy traffic: by campaign, by ad set, by the page an ad points to. A dimension of your own turns that into a few levels a slot can learn from. It reads one field of the visit’s arrival:
- A campaign tag. Ad sets named by who they reach, a spring sale against a brand campaign, a creative against another.
- The landing path. Visitors who arrived on
/giftsagainst those who arrived on a product. - The referrer. A partner’s site, a newsletter, a review that sent you traffic.
- The country.
Spellhome names its ad sets by who they reach, dog_…, home_…, gift_…, and puts that name on every ad’s link. Meta fills it in with utm_content={{adset.name}}, and the same tag goes on Google’s ad groups, so both networks fold into the same three audiences.
In your coderoutingcat.config.ts · routingcat/pdp.ts
routingcat.config.ts
dimensions: {
avatar: {
from: 'utm_content',
levels: { first_dog: 'dog_*', homeowner: 'home_*', gift: 'gift_*' },
},
},routingcat/pdp.ts
lead: slot({
default: 'single',
personalizeBy: ['avatar'],
variants: { /* the offers */ },
}),For example, you ask
Our ad sets are named by audience: dog_, home_ and gift_, passed in utm_content. Declare that as a dimension called “avatar”, and let the lead slot learn by it.
dimensions turns raw ad set names into a handful of audiences: ad sets come and go, the audiences stay. personalizeBy adds a dimension of yours, or the ad network, to what a slot learns from.
What you see
Results break down by any dimension. Spellhome’s offers, by audience:
First-dog owners45% of visits
+6%shown 14%−33%shown 4%+29%shown 67%−30%shown 4%−11%shown 6%Homeowners25% of visits
+28%shown 16%+11%shown 5%+2%shown 8%+46%shown 49%+24%shown 18%Gift buyers15% of visits
+38%shown 8%+22%shown 5%+16%shown 6%+16%shown 6%+67%shown 72%Untagged visits15% of visits
+5%shown 30%−21%shown 5%+0%shown 23%−22%shown 9%−6%shown 21%Everyone
+16%−11%+21%+12%+33%Each audience has its own leader. First-dog owners are shown the starter offer two times in three, where it is predicted at +29%. Homeowners get the full collection half the time, at +46%. Gift buyers get the gift offer nearly three times in four, at +67%.
Now look at the collection across all visitors: +12% [−5, +30], behind three other offers. Judged on everyone, it would have been dropped. It is the best offer Spellhome has for a quarter of its traffic.
Worth knowing
- Each level needs its own evidence. Every level splits the conversions. Three or four audiences on 100,000 visits a month learn in weeks. Forty ad sets as forty audiences would learn nothing. A slot takes at most four dimensions in
personalizeBy. - Comparisons stay fair without you. Ad sets convert at very different rates. A variant that goes live during a strong campaign’s burst is compared among comparable visits, and is not credited with that campaign’s buyers.
- You can turn it off. With personalization off, a slot finds one answer for everyone. It is the right setting for a price (chapter 7).
- Tag your Meta ads as paid. Meta marks unpaid clicks too, so a visit counts as
metaonly from a link with a source and a paid medium, asutm_source=facebook&utm_medium=paid_social. - Learning is not deciding. When you already know who a variant is for, or must guarantee it, the next chapter has the tools.
Read more: Show a variant only to some visitors · Routing settings
4 min
Rules and beliefs
Three ways to say who a variant is for: only, never and expect. Two are rules that always hold. One is a belief the results can overturn.
The last chapter let routing find out who prefers what. Sometimes you already know, or you have to decide. A version made for one audience should not show to another. A promise made in one ad should not greet visitors who never saw it. RoutingCat has two tools for this, and they are easy to confuse.
A rule: only and never
A condition says where a variant may show. only names the visitors it is for. never names the visitors it must not reach. A visitor it excludes is routed among the slot’s other variants, as if this one did not exist.
What it is for:
- A version made for one audience. Spellhome’s candles are sold to dog owners, and some of its ad sets reach cat owners. For them, a first photo with a cat in it, shown only to visitors from those ad sets.
- A promise an ad made. The hero that says “50% off”, only for the campaign whose ad promised it.
- Something that breaks somewhere. The sticky “Add to cart” bar, never on desktop.
- An offer that is not for everyone. The first-order discount, never on a repeat visit.
- A claim with a boundary. A delivery promise, only for visitors from the countries it covers. A compliance badge, only for the campaign aimed at larger companies.
lifestyleat home, with a dog

catat home, with a cat

In your coderoutingcat/pdp.ts
routingcat/pdp.ts
photo: slot({
default: 'jar',
variants: {
jar: variant({ fields: { photo: jar } }),
lifestyle: variant({ fields: { photo: atHome } }),
cat: variant({ fields: { photo: catAtHome }, only: { avatar: 'cat' } }),
},
}),For example, you ask
Add a cat version of the first photo on the product page, shown only to visitors from our cat_ ad sets.
Here avatar is the dimension of chapter 8, with one more level for the cat ad sets: cat: 'cat_*'.
never is written the same way, as never: { visit: 'repeat' } or never: { device: 'desktop' }. A condition can name several levels, combine dimensions, and be named once as an audience to reuse wherever a condition goes:
In your coderoutingcat.config.ts
routingcat.config.ts
audiences: {
meta_mobile: { device: 'mobile', ad_network: 'meta' },
},sticky: variant({ fields: { bar: 'sticky' }, only: 'meta_mobile' }),On a variant that is already live, a condition can be set without a deploy, within about two minutes: npx routingcat set pdp/photo:cat --only avatar=cat.
A belief: expect
An expectation is where you believe a variant does better. From its first visitors, routing shows it more there and less elsewhere. Then the evidence corrects the belief, whichever way it goes.
Spellhome wrote three offers, each for one of its audiences, and said so:
In your coderoutingcat/pdp.ts
routingcat/pdp.ts
starter: variant({ fields: { selected: 'single', guarantee: 'inline' }, expect: { avatar: 'first_dog' } }),
collection: variant({ fields: { selected: 'set', order: 'set-first', price: 'each' }, expect: { avatar: 'homeowner' } }),
gift: variant({ fields: { selected: 'pair', order: 'pair-first', note: 'keep-give' }, expect: { avatar: 'gift' } }),starterfor first-dog owners

collectionfor homeowners

giftfor gift buyers

All three beliefs held: each offer leads with the audience it was written for. Had one been wrong, routing would have found out. A right expectation saves some early exploring, and a wrong one is overturned as conversions come in.
Which one
Ask one question: must it hold, or do I believe it?
| You have | You declare | What routing does |
|---|---|---|
| No idea who prefers what | personalizeBy | Learns it, audience by audience |
| A belief: “homeowners will take the set” | expect | Starts from your belief, and corrects it with conversions |
| A rule: the ad promised it, or it breaks elsewhere | only, never | Shows the variant where you said, and nowhere else, always |
Use a rule only for what must hold. A rule is never tested: an offer shown only to gift buyers will never show whether homeowners wanted it too.
What you see
status prints each variant’s conditions under its line. A variant with a condition is judged on the visits it may show to, against the default on those same visits, so its figures mean “for this audience”. The measured gain includes your rules, since the control still sees the defaults.
Worth knowing
- The default takes no condition. It shows wherever no other variant may, and to the control.
- A visitor keeps what they saw. A condition is checked when a visit starts, so a new one reaches the visits that begin after it. To stop a variant at once, pause it (chapter 11).
- Read the levels exactly. A tablet is
other. Soonly: { device: 'mobile' }leaves tablets out, andnever: { device: 'desktop' }keeps them. - A rule can exclude everyone: a typo in a tag, a campaign that ended.
planrefuses a condition no visitor could match, andstatuswarns when no visit of the last 28 days did. A variant made for a campaign outlives it: retire it when the campaign ends. - On a price, a rule is a decision. A price shown only to one campaign’s visitors is ordinary commerce. A price by device or by hour is one you could not explain to a customer. Chapter 7 says where the line is.
- Every deployment of your site must run RoutingCat 1.1. Until they all do, a variant with a condition is held back and shown to no one, so a rule never shows outside its condition.
Read more: Show a variant only to some visitors · Config and declarations
Read and act3 min
Results and the measured gain
Three kinds of number, each with how sure it is, and one line that says what routing added.
Every number RoutingCat shows says what kind it is. Once you know the three kinds, every screen reads the same way, in the terminal and in the dashboard.
Three kinds of number
- Counted is what happened: visits, conversions, revenue, the share of visits each variant was served. It is exact. It is not a fair comparison between variants, because routing showed each one to different visitors at different times.
- Predicted is the model’s belief about each variant, with a range:
+17% [+2, +31]. It moves as conversions arrive, and early extremes shrink. - Measured comes from the random control, without the model. It is the only number that counts as a gain.
Reading a page
For example, you ask
How is the product page doing?
Your agent
Bashnpx routingcat status pdp
pdp · objective purchase, 24 h · control: the defaults, 10%gain +$5,258 profit [−7,573, +18,088] · measured41,590 routed / 4,610 control visitslead default single · 3 livesingle default VPV (profit) $1.68 [1.48, 1.89] · served 22%pair favored VPV (profit) $1.96 [1.72, 2.20] · served 57%+17% [+2, +31] vs default+17% VPV (profit): +4% conversions, +12% per conversionset learning VPV (profit) $1.58 [1.32, 1.85] · served 20%−6% [−21, +10] vs default−6% VPV (profit): −22% conversions, +21% per conversion
The pair leads. The model predicts a visit that opens on it is worth about 17% more than one that opens on a single candle, somewhere between 2% and 31% more. The set sells about a fifth fewer orders, each worth a fifth more, and comes out slightly behind: too early to call. The measured gain is also too early to tell: somewhere between $7,573 less and $18,088 more than the old page.
That is Spellhome’s first slot, two weeks after it went live. Line by line:
gainis the measured gain: routed visits against the control, in your own unit, with its range.- Each variant has a label, what a visit that sees it is predicted to be worth, its difference from the default with a range, and the share of visits it is served.
favoredmeans routing shows it most.learningmeans routing has evidence of its own for it, andpriorthat it does not yet. None of them is a verdict.- The last line under a variant splits its difference in two: how many visitors convert, and what a conversion is worth (chapter 4).
The full output also gives each variant a signal from 0 to 100, which says how much evidence is behind its prediction. It is not a chance of being the best.
Read the range, not the point
Every prediction and every gain comes with a 95% range: where the evidence puts the true figure. Three habits follow:
- A range that spans zero means the direction is not known yet.
−6% [−21, +10]is not “6% worse”. It is “too early to call”. - Compare variants by their predicted difference from the default, never by their counted rates.
- Decide from the predictions, which settle sooner, and report the measured gain, which settles last.
Routing does not wait for any of this. At Spellhome the pair already had more than half the visits at two weeks, because it was already the likeliest to be the best.
The measured gain
One visitor in ten never leaves your defaults. The measured gain is the difference between everyone else and them.
+$126,162
of profit added in twelve weeks, somewhere between $94,503 and $157,821
A routed visit$2.22
A control visit$1.71
The range the evidence supports
Your agent
Bashnpx routingcat status pdp
pdp · objective purchase, 24 h · control: the defaults, 10%gain +$126,162 profit [+94,503, +157,821] · measured+30% [+22, +37] VPV vs control+626 conversions (purchase) vs defaults [+91, +1,161]249,347 routed / 27,853 control visitsrouted VPV (profit) $2.22, control $1.71conversion rate 3.3%, control 3.1%$67 per conversion, control $56
Read it from the top:
gainis what routing added over showing everyone the defaults: $126,162, and the evidence puts it between $94,503 and $157,821.- Most of it is order value. Routed visitors buy a little more often, 3.3% against 3.1%. What changed is what they buy: $67 an order against $56.
- It is measured, not modeled. Those 27,853 control visits are the reason you can repeat the number to a partner or a board.
Worth knowing
- The gain is recomputed every day, and it is not a finish line. Acting on its first good day overstates it. Read it weekly.
- It settles slowly. Its precision depends on the size of the control. At a 3% conversion rate, confirming a 20% gain takes about 150,000 visits to the page. On a small site it can read “too early to tell” for months, while the predictions already guide your decisions.
- It includes the cost of trying things. Weak variants and cautious new ones pull it down, so it is what you actually kept.
- Pages' gains do not add up, since one visitor sees several pages.
- The dashboard shows the same figures as the command, and states no verdict either.
Read more: What the numbers mean · Read and explain results · The dashboard
2 min
Change what is live
Pause, retire, promote or revert from the command line. It is live within about two minutes, with no deploy.
A new variant reaches visitors through a deploy. What you do to it afterwards does not need one. An act changes what visitors see within about two minutes.
What it is for
- Pause, when something is wrong with a variant and not with its idea: a broken image, a typo in a price, a claim to check. It is off in two minutes, and
resumebrings it back. A pause holds through every deploy. - Retire, when a variant has clearly lost. Spellhome retired the version of its offer with no saving shown, at
−25% [−39, −12]. It stops for good, keeps its history, and frees room for a new idea. - Promote, when a variant clearly leads for everyone. It becomes its slot’s default: the control sees it from then on, and new ideas are compared with it. The gain measured so far is kept, and measurement goes on against the new defaults.
- Revert, when a change made the page worse and you want it as it was. The page returns to what it served before its last change.
Two more, for the whole site or for one audience: set routing=off makes every page serve its defaults to everyone, and loses nothing routing learned. set with --only or --never puts a condition on a live variant (chapter 9).
How it runs
For example, you ask
Promote the lifestyle photo, and update the code to match.
Your agent
Bashnpx routingcat promote pdp/photo:lifestyle --dry-run
promote · dry run: nothing written · registry version 31would changepdp/photo default: jar → lifestyleera boundary pdp
Bashnpx routingcat promote pdp/photo:lifestyle --expect 31
promote · done · registry version 32pdp/photo default: jar → lifestyle
Editroutingcat/pdp.ts
Promoted. The lifestyle photo is now the default, so the control sees it too, and the gain measured so far is kept. I set the slot’s default to lifestyle in routingcat/pdp.ts, so the next deploy agrees with what is live.
Every act can be rehearsed. With --dry-run it writes nothing and prints what it would change. With --expect, it refuses to run if anything changed on the site since you looked.
Then make the code agree
An act holds until a deploy changes the same thing in your code. So after an act, the page’s file follows:
| After | In the page’s file |
|---|---|
promote | Set the slot’s default to the promoted variant |
retire | Delete the variant |
pause, resume | Nothing: a pause holds until you resume |
revert | Return the file to what is live |
Where what is live and your code differ, status says so on a line of its own, under drift.
When to act
Routing already does most of the work: once it knows a variant is worse, the variant gets few visits. What is left to you:
- Retire a variant that is clearly worse, and not better for any kind of visitor.
- Pause or retire at once a variant that is wrong: off-brand, mistaken, broken. That needs no evidence.
- Promote a clear leader, so the control stops paying for the old default.
- Replace what you retire. What the leaders have in common is the next idea.
Worth knowing
- A default cannot be paused or retired. Promote another variant first.
- A variant with a rule or a belief cannot be promoted, since the default shows to everyone. Remove them first, if you want it everywhere.
- A visitor who was shown a paused variant gets the default for the rest of their visit.
- An act skips code review. Say in your agent instructions who may run one, and when.
Read more: Change what’s live · Routing settings
2 min
Run it every week
A short weekly loop: read, decide, build, ship. The page keeps routing in between.
A routed page is never finished. Offers age, ads change, a competitor moves. What keeps a page improving is a routine, and it is a short one.
Read
What changed this week, and how sure each number is.
Decide
What to retire, what to promote, what to try next.
Build
Your agent writes the variants in your code.
Ship
You read the plan and deploy. Live in two minutes.
Then back to Read, a week later. In between, the page routes every visit and learns from every order.
The loop
- Read. What changed since the last look, and whether anything is broken. A figure built on broken tracking means nothing, so warnings come first.
- Decide. What to retire, what to promote, what to leave alone (chapter 11).
- Build. The next ideas, from what the leading variants have in common, and from outside the page: how similar sites present the same thing, what your ads are promising, what customers write to you.
- Ship. Read
plan, then deploy.
Once a week is the right rhythm. Evidence builds over days and weeks, and looking more often mostly shows noise.
What a review looks like
For example, you ask
Review the product page for the week: what changed, what you would retire, what you would keep, and what you would try next. Before you propose anything, look at how stores like ours present bundles, and at our orders by ad set.
Your agent
Bashnpx routingcat status pdp
Bashnpx routingcat show pdp/lead:collection --by avatar
Readexports/meta-adsets-last-28d.csv
This week: no deploys, no warnings. Three things.
1. Retire the set as a lead offer. Across everyone it is at −11% [−21, −1], and the collection offer does its job better with homeowners.
2. Keep both cart suggestions running. Each adds about 4% to an order and neither has cost orders. Nothing to decide.
3. Next, I would try automatic refills. One store in three I looked at pre-selects them. It would lower the first order and add repeat ones, so before we route it we should value a refill order at what a subscriber brings. Tell me how many shipments a subscriber stays for, and I’ll set it up.
Want me to retire the set and draft the refill offer?
However you work with your agent, a review comes down to the same thing: one look a week, and a decision.
Which traffic is worth buying
The same records say where conversions came from. npx routingcat sources counts them by channel, by source, by campaign and by landing page, and results break down by your own audiences. It is accounting: it says where conversions came from, not what caused them.
Spellhome’s last four weeks, by audience:
| Audience | Visits | Orders | Rate | Profit | Per visit |
|---|---|---|---|---|---|
| First-dog owners | 41,430 | 1,583 | 3.8% | $99,194 | $2.39 |
| Homeowners | 23,176 | 749 | 3.2% | $55,995 | $2.42 |
| Gift buyers | 14,099 | 667 | 4.7% | $47,769 | $3.39 |
| Untagged | 13,695 | 400 | 2.9% | $26,502 | $1.94 |
A gift visit is worth $3.39, about 40% more than a first-dog or a homeowner visit. If gift ad sets cost the same per click as the others, they are where the next ad dollar should go. The page now says which traffic is worth buying, in profit and not in clicks. Your ad accounts never had that number.
Handing it over
The loop can be your agent’s. When you hand it the routine, it keeps the rhythm, fixes or raises what is broken, judges each slot, acts within what you allowed, and reports each time: what it changed, why, with the evidence and its ranges, and what it expects to learn next. It stays inside what you reserved, such as your brand, your claims or your prices, and asks before a change would cross it.
If you are starting
- Install it on the page that takes your paid traffic, and say what a conversion is worth to you.
- Start with the offer. It is where the money is.
- Try ideas, not wordings: two to four bold variants a slot, sized to your traffic.
- Read ranges, not points, and the split between how many convert and what a conversion is worth.
- Retire, promote, replace. Weekly.
- Name your audiences once the page has a clear best version for everyone.
Read more: Read and explain results · Work with your coding agent
Run it safely3 min
Speed, failure, privacy
Your site comes first: it stays fast, it stays up, and your visitors stay anonymous.
RoutingCat sits in front of the pages that earn your living. Three questions decide whether that is acceptable: what it costs in speed, what happens when it fails, and what it knows about your visitors.
Speed
On Next.js, Nuxt and Astro, each visitor’s variants are picked on your own server before the page renders, from routing data refreshed in the background. No call to RoutingCat waits on the page, and the page arrives already in its variant, with no flicker.
- RoutingCat’s own work takes about a millisecond per page. On Vercel it runs as Routing Middleware, which adds from nothing measurable to about 20 ms depending on the region. On Cloudflare it adds 2 to 5 ms of CPU.
- Static pages stay static. Each combination of a static page is built and cached, and the visitor is sent to theirs before the cache.
- The browser code is about 11 KB, gzipped, on a page’s first load. It reports what the visitor was actually shown.
- On any other host, a script picks the variants in the browser before the page paints. A visitor’s first view waits for routing data, one second at most by default, then shows the defaults.
Failure
Visitors always get a page.
- If RoutingCat cannot be reached, routing goes on with the routing data each server last had. A new server waits up to 300 ms, then starts from the copy built into your deploy.
- If a build cannot reach it, the build ships as usual and shows your defaults.
- The tracking calls never throw, and send after the response, so they never delay or break a checkout.
- Every slot keeps its default, which is your site as it was.
- You hold the switches. A pause stops one variant within about two minutes.
set routing=offreturns the whole site to its defaults, and keeps everything routing learned.
What reaches production
- A preview deploy changes nothing live. It registers the new versions, and its own visits stay out of your results.
- In development, RoutingCat serves the defaults and records nothing.
- A production deploy is the only way a new variant reaches visitors. The first RoutingCat line of its build log says what went live.
Privacy
RoutingCat personalizes on the context of a visit, never on who the visitor is.
- A visitor is a random id in a first-party cookie, written by your own server. It is random for each site and shared with none, so there is no profile of a person across sites.
- What it never stores: a visitor’s full IP address, email addresses, the contents of forms, a fingerprint, a precise location.
- What it records: the time, an approximate location, the device and browser, where the visit came from, what was shown, and the events you send.
- Crawlers, link previews and monitoring are left out of learning and results.
- The cookie is set for every visitor, whatever consent your site collects. The docs list every cookie and how long it lasts.
Keep personal data out of the events you send: they are kept as you send them.
Plans
Hobby is free: up to 10,000 visitors a month on routed pages, and 10 live variants across your sites. Pro is $100 a month: 100,000 visitors included, 100 live variants a page, and learning up to every hour, where Hobby learns once a day.
Read more: Run it in production · What RoutingCat collects · How RoutingCat works