Config and declarations
routingcat.config.ts names your site and its conversions. Each page RoutingCat can vary is declared in its own file in routingcat/, with page(), slot() and variant(). All four come from routingcat/next, and check what you declare when your code loads.
routingcat.config.ts
npx routingcat init writes it; you or your agent add the objective and the outcomes. From the example app:
import { config } from 'routingcat/next'
export default config({ site: 'routingcat-example', publicKey: 'pk_routingcat-example_NcMQQWyZ', // public: names the site; write-only objective: 'waitlist', // the main conversion: joining the waitlist, on the home and pricing pages // tracked beside it: a paid first month, from Stripe's checkout webhook; "Talk to us" on /pricing, // captured in PostHog and forwarded by a PostHog destination outcomes: ['purchase', 'contact_sales'],})| Option | Type | Default | What it does |
|---|---|---|---|
site |
string |
Required | The site’s key, as npx routingcat init wrote it. |
publicKey |
string |
Required | The site’s public key, pk_ and the site’s key and 8 characters, as init wrote it. It names the site and only writes events, so it is safe in code. |
origin |
string |
The Vercel project’s production address | The site’s production address, https:// and its host: https://www.example.com. Each production build sends it, so RoutingCat counts the site’s own domain and links previews to it. Vercel’s build sends the project’s own instead. |
objective |
objective | None: a routed page then needs its own | The main conversion, which routing learns to get more of, on every page without its own. See Objectives. |
outcomes |
string[] |
None | Events tracked beside the objective, by name: counted and reported, never steering routing. |
Objectives
An objective takes one of three forms, on the site or on a page. Its window is how long after a visitor first sees a page a conversion still counts for it: seconds, or a number then s, m, h or d. Limits and defaults gives its default and range.
| Form | Example |
|---|---|
| One event | objective: 'signup' |
| Several, where any one counts | objective: ['signup', 'trial_started'] |
| With its window | objective: { events: 'signup', window: '48h' } |
An objective names at most 32 events. A page with two or more live variants needs one, its own or the site’s.
page()
page(key, options) declares a page: its key, its routes and its slots, in a file of routingcat/, one page per file. From the example app:
import { page, slot, variant } from 'routingcat/next'
export const home = page('home', { routes: ['/'], slots: { hero: slot({ default: 'current', variants: { current: variant({ fields: { headline: 'Route every visitor to what converts' } }), learns: variant({ fields: { headline: 'Your pages learn what converts each visitor' }, description: 'Lead with learning', }), shows: variant({ fields: { headline: 'Show each visitor the page that converts them' }, description: 'Lead with what each visitor sees', }), tries: variant({ fields: { headline: 'Try your ideas on real visitors, keep what works' }, description: 'Lead with trying ideas', }), }, }), subhead: slot({ default: 'current', variants: { current: variant({ fields: { text: 'Each visitor sees the version of the page most likely to convert them, and you see which version works for whom.', }, }), short: variant({ fields: { text: 'Every visitor gets the page that works for them. You see what works.' }, description: 'The same promise, shorter', }), proof: variant({ fields: { text: 'Every change is measured on your own visitors, so you see the proof before you keep it.', }, description: 'Lead with measured proof', }), plain: variant({ fields: { text: 'RoutingCat picks the best version of your page for each visitor.' }, description: 'One plain sentence', }), }, }), },})| Option | Type | Default | What it does |
|---|---|---|---|
routes |
string[] |
Required | The paths the page serves, written as Routes says. At least one; a path belongs to one page. |
objective |
objective | The site’s | The page’s own objective, in place of the site’s. See Objectives. |
slots |
object: slot key → slot() |
Required | The places on the page that can vary, by key. |
Routes
Routes are written in Next.js’s notation: /, /pricing, /blog/[slug], /docs/[...slug], /[[...slug]].
slot()
slot(options) declares a place on the page that can vary. Slots vary independently, so every combination of their variants must render.
| Option | Type | Default | What it does |
|---|---|---|---|
default |
string: a variant key | Required | The default: the variant the slot shows today, which the control always sees. One of variants. |
variants |
object: variant key → variant() or a component |
Required | The variants, by key: at least one. A component alone stands for variant({ component }). The order they are listed in is kept. |
renderer |
{ key, schema? } |
None | For variants made of fields: names what renders them, and the fields’ schema, the slot’s contract. Changing the schema changes the slot, never its variants’ versions. |
fixedShare |
number |
0 | The share of the slot’s routed visits split evenly, or by weights, keeping each visitor on the same variant across visits: from 0 to 1. At 1, a classic A/B test. |
weights |
object: variant key → number | Even | The fixed share’s split, by variant key: numbers from 0 to 1,000,000, relative to each other, one of them positive. A variant left out weighs 0 and stays out of the split. |
personalization |
'on' | 'off' |
On | off routes every visitor of the slot the same way. |
floor |
number |
Automatic | The smallest chance each variant keeps of being picked for a routed visit outside the fixed share: from 0 to 1. |
cap |
number |
Automatic | The largest chance each variant can have of being picked for a routed visit outside the fixed share: from 0 to 1. |
name |
string |
None | A name for people and agents. |
description |
string |
None | What the slot is for. |
tags |
string[] |
None | Labels to organize slots. |
The routing settings fixedShare, personalization, floor and cap can also change with npx routingcat set, without a deploy; a later deploy that changes one in code applies the code’s. weights change in code.
variant()
variant(options) declares one alternative for a slot: made of fields, or a component.
Made of fields
| Option | Type | Default | What it does |
|---|---|---|---|
fields |
object | Required | The content the page renders: JSON values, or images imported from your repository. |
A component
| Option | Type | Default | What it does |
|---|---|---|---|
component |
React component | Required | The component that renders the variant. |
content |
object | None | What the component displays, declared as fields are, so that the variant’s versions and diffs show it. |
Either kind
| Option | Type | Default | What it does |
|---|---|---|---|
parent |
string | string[] |
None | The variant this one was made from, by key, or a version of it, as quickstart@v3; several for a recombination. |
relation |
'fix' | 'refine' | 'fork' | 'recombine' |
None | How it relates to its parent. Required with one parent; several read as recombine. |
name |
string |
None | A name for people and agents. |
description |
string |
None | What the variant tries. |
tags |
string[] |
None | Labels to organize variants. |
learningGroups |
object: name → string | None | Groups you define, such as { angle: 'benefit-first' }, apart from tags. Every change is recorded; routing does not use them. |
Rules
The functions refuse a declaration that breaks one of these when your code loads, naming its address.
- Each function takes the options above alone: one it doesn’t take is refused, naming it and the option meant, as
fixedShareforfixed_share. - A key, of the site, a page, a slot or a variant, is lowercase letters, digits,
-and_, at most 40 characters. - A page lists at least one route, each once, each a path from
/. - A route’s segments are each written as itself, or as a name in brackets alone:
[slug]for one segment, and, last,[...slug]for the rest of the path or[[...slug]]for the rest or none.:slugand*are refused, with the brackets to write. - A slot’s default is one of its variants.
fixedShare,floorandcapare shares, from 0 to 1.weightsname the slot’s own variants, with numbers from 0 to 1,000,000.- One of the
weightsis positive: a variant they leave out weighs 0, outside the split. - A variant is made of
fieldsor acomponent, never both. - A variant with one
parentstates itsrelation; several parents read asrecombine. config()’spublicKeyis the public key of the site it names.config()’soriginis an address alone,https://and a host in lowercase, with no path.