Skip to content

Config and declarations

routingcat.config.json names your site, its pages and its conversions. Each page’s slots and variants are written in your templates, in the slot markup, which npx routingcat push reads and stamps.

routingcat.config.json

npx routingcat init writes it, with your site, its public key and where your templates are; you or your agent add the pages, the objective and the outcomes.

From the example app:

{
"$schema": "https://cdn.routingcat.com/schemas/1.0.3/config-1.json",
"site": "routingcat-static-example",
"public_key": "pk_routingcat-static-example_Tc5Wm9Rb",
"integration": "cloudflare",
"sources": ["public/**/*.html"],
"head": { "partial": "routingcat-head.html" },
"objective": "waitlist",
"pages": {
"home": { "routes": ["/"], "url": "/" },
"pricing": { "routes": ["/pricing"], "url": "/pricing" }
},
"variants": {
"home/hero:learns": { "description": "Lead with learning" },
"home/hero:shows": { "description": "Lead with each visitor's own page" },
"home/subhead:short": { "description": "Half the words" },
"pricing/lead:start": { "description": "Lead with the free start" },
"pricing/trial:days30": { "description": "A longer trial" }
}
}
Field Type Default What it holds
$schema string None The address of the file’s JSON Schema, which your editor reads: https://cdn.routingcat.com/schemas/<version>/config-1.json, as push writes it for the routingcat version that runs it. A command refuses a file whose config-<n> is newer than its own, naming the version that reads it.
site string Required Your site’s key, as npx routingcat init wrote it.
public_key pk_ id Required Your 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 your pages.
integration vercel or cloudflare Required Who decides your pages, as init wrote it: vercel, Vercel’s middleware, or cloudflare, a Worker or a Pages middleware.
environments.production.hosts list of string None Your production host, which your build on Cloudflare sends, as init writes it. On Vercel, the project gives it.
sources list of string Required Where your templates are: globs from your project’s folder, as resources/views/**/*.blade.php, in the syntax of Node.js’s fs.glob: *, **, ?, [abc] and {a,b}; a file or folder whose name starts with . is left out. push reads the slot markup in the files they match.
head.partial string None Where push writes the head partial, which your layout includes. None where your stack’s own config writes the head, as Nuxt 2’s nuxt.config.js does.
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 list of string None Events tracked beside the objective, by name: counted and reported, never steering routing.
pages object Required Your routed pages, by key.
pages.<key>.routes list of string Required The paths the page serves, with brackets for what varies: /pricing, /blog/[slug] for one segment, /docs/[...slug] for the rest of the path. At least one; a path belongs to one page. A path names every spelling of its page: /pricing is also /pricing/, /pricing.html and /pricing/index.html.
pages.<key>.url string None One path of the page on your production host, which npx routingcat doctor reads to check the page as served.
pages.<key>.objective objective The site’s The page’s own objective, in place of the site’s. See Objectives.
slots object None Each slot’s routing, by its address, as home/hero.
slots.<address>.fixed_share 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.
slots.<address>.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.
slots.<address>.personalization on or off on off routes every visitor of the slot the same way.
slots.<address>.floor number Automatic The smallest chance each variant keeps of being picked for a routed visit outside the fixed share: from 0 to 1.
slots.<address>.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.
variants object None Each variant’s parent, name, description and tags, by its address, as home/hero:learns.
variants.<address>.parent string or list of string None The variant this one was made from, by key, or a version of it, as current@v2; several for a recombination.
variants.<address>.relation fix, refine, fork or recombine None How it relates to its parent. Required with one parent; several read as recombine.
variants.<address>.name string None A name for people and agents.
variants.<address>.description string None What the variant tries.
variants.<address>.tags list of string None Labels to organize variants.

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.

The slot markup

A slot is an element of your template, its container, around its variants, each an element directly inside it. You write the keys and hidden; push writes the rest. From the example app, public/pricing.html, a line in a paragraph of its own and a word inside a sentence:

public/pricing.html
<h1>Pricing</h1>
<div data-rc-page="pricing" data-rc-slot="lead" style="display:contents">
<p data-rc-variant="growth" data-rc-hash="b0918105ecaa53a328a4053a95c7f879e1296ea4e38c566ff8a21a2a2d3818ed" style="display:contents" class="lead">Free while you grow, then by billed visitors</p>
<p data-rc-variant="start" data-rc-hash="43059d727593cbc63e95432acd1a62d6b5406187fd79e98c4603d08ee63b6d35" class="lead" hidden data-nosnippet>Start free; pay only for the visitors you bill</p>
</div>
<p>Start your <span data-rc-page="pricing" data-rc-slot="trial" style="display:contents"><span data-rc-variant="days14" data-rc-hash="55e70241ccd8f3434567706bbe2cfec639c4a80c72096d941822d2f79fa1e95f" style="display:contents">14-day</span><span data-rc-variant="days30" data-rc-hash="196d9ca52c6ba10b0bd7d6838fbc4e30cb89b84cd4290e524da094871695c691" hidden data-nosnippet>30-day</span></span> trial.</p>

In a Nuxt 2 app, the markup goes in a .vue file’s template, as in the Nuxt 2 example:

pages/index.vue
<template>
<main>
<div data-rc-page="home" data-rc-slot="hero" style="display:contents">
<div data-rc-variant="current" data-rc-hash="02d1e9e4e66d2bb95e87b441483a1740ac92b73e247614c095ac42c186eb21c2" style="display:contents">
<h1>Studio headshots from your selfies</h1>
</div>
<div data-rc-variant="minutes" data-rc-hash="34e53dd1d19c668e40320c7dd977d07324c1798e31f163037e068b7080df2b1c" hidden data-nosnippet>
<h1>Professional headshots in minutes</h1>
</div>
</div>
<p><NuxtLink to="/pricing">See the pricing</NuxtLink></p>
</main>
</template>
<script>
export default {
name: 'HomePage',
}
</script>
Attribute On What it holds
data-rc-page The container The page’s key, as pages names it in routingcat.config.json
data-rc-slot The container The slot’s key
data-rc-variant Each variant The variant’s key
hidden Each variant but the default Keeps the variant hidden where RoutingCat doesn’t run
data-rc-hash Each variant The version of its content, which push writes
style="display:contents" The container and the default Keeps the slot from adding a box to your layout; push writes it where it is missing
data-nosnippet Each hidden variant Keeps its text out of the snippets search engines show; push writes it after hidden
data-rc-lcp One <img> of a variant The variant’s main image, which RoutingCat preloads for whichever variant shows, so that no variant loads faster than another

What push stamps

npx routingcat push reads the markup in every file sources matches, and in each start tag:

  • writes each variant’s data-rc-hash, right after its data-rc-variant;
  • adds style="display:contents" to a container or a default that lacks it;
  • adds data-nosnippet after hidden, on a hidden variant that lacks it.

It never adds or removes hidden, and stamping twice changes nothing. push --check changes nothing, and fails while a stamp is due, as your build does, so that a deploy never reports other versions than it registers.

Rules

npx routingcat push, plan and your build refuse a file or markup that breaks one of these, naming its address or its template’s line.

routingcat.config.json

  • public_key is the public key of the site site names.
  • An outcome is listed once, and a route once in its page.
  • slots and variants name pages declared under pages.
  • A slot’s weights are numbers from 0 to 1,000,000, one of them positive: a variant they leave out weighs 0, outside the split.
  • It holds the fields above alone: a field it doesn’t know is refused, naming it and the field meant, as fixed_share for fixedShare.
  • 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. :slug and * are refused, with the brackets to write.

The slot markup

  • A container carries data-rc-page and data-rc-slot, and each variant data-rc-variant, each key written as itself, never as a template expression: lowercase letters, digits, - and _, at most 40 characters. See markup the scan can’t read.
  • Every child of a container is a variant, each key once; between them, only whitespace and comments. See markup that breaks the grammar.
  • Exactly one variant, the default, has no hidden: what your page shows today, which the control always sees, and so does a visitor whose browser runs no script. See markup that breaks the grammar.
  • Every other variant carries hidden, written alone, and no display in its style, which would show it where RoutingCat doesn’t run. See markup that breaks the grammar.
  • The container and the default are display:contents, as push writes them, and declare no other display. See markup that breaks the grammar.
  • Every image inside a variant, the default’s included, is loading="lazy" and never fetchpriority="high", so that no hidden image is fetched; data-rc-lcp marks one <img> at most per variant. See markup that breaks the grammar.
  • Every copy of a slot in your templates carries the same variants, in the same order, with the same content. See markup that breaks the grammar.
  • Every tag a variant opens closes inside it, in the same branch of a conditional; and slots stand in templates in HTML’s syntax: a template indented without tags, as Haml, Slim or Pug, includes an HTML partial that holds its slots. See markup the scan can’t read.
  • A slot stays where the browser leaves it: inside a paragraph, its container and its variants are span elements; a container is never directly inside table, thead, tbody, tfoot, tr, select, ul, ol or dl, but inside the cell or the item, or around the whole; never inside another slot’s variant; and never in <head>. See a slot the browser moves.
  • A variant holds nothing that acts while it is hidden: no script that runs, no structured data, no stylesheet, no id given elsewhere on the page, and one h1 at most, only in a slot that holds the page’s heading. See what acts while hidden.
  • A form in a variant other than the default draws a warning, since it submits even while hidden: it must work once shown, and do nothing until then. See what acts while hidden.
  • Each page your markup names is declared under pages in routingcat.config.json, whose slots and variants name only slots and variants your markup carries. See markup and a config that disagree.