Set up RoutingCat
This tutorial takes a Next.js site on Vercel from nothing to a working install, in four steps. Your agent does most of the work; you approve a sign-in, choose what to track, deploy and add a card, with nothing charged. One real conversion of your own, recommended and your choice, confirms your tracking end to end. Your visitors see no change, because no page is routed until you declare one.
It follows Lumen, a photo-editing app with Clerk, Stripe and PostHog. What your site needs lists the requirements.
1. Install with init
In your Next.js app’s folder, you or your agent run:
npx routingcat initIt checks your project first, and stops before changing anything when something needs you:
- Next.js 16.2.6 or later, with an
app/folder. Upgrading is your call; the message gives Next.js’s upgrade command. - No
middleware.ts, which Next.js 16 renamedproxy.ts. The message gives Next.js’s command to rename yours. - A folder linked to a Vercel project, and the Vercel CLI signed in: run
npx vercel linkornpx vercel login, theninitagain. - One app. In a monorepo, run it in the app’s own folder.
It signs you in. A browser page opens, where you sign in, or sign up, and allow the command on this machine. Where no browser can open, init prints the link, waits 50 seconds and exits with code 3: open the link, approve, and run init again, which continues with the same link.
It shows its plan, then applies it. At a terminal it asks whether to add a note to AGENTS.md, then whether to apply the plan; run by an agent, it asks nothing, applies the plan and prints what changed. --dry-run stops after the plan. On Lumen’s project, run by an agent and signed in already:
routingcat 1.0.0 · <project>
Checks ✓ Next.js 16.3.8, App Router (app/) · pnpm ✓ Vercel project lumen · Vercel CLI signed in as emma ✓ proxy.ts: clerkMiddleware() with Clerk's usual matcher → composes ✓ signed in to RoutingCatDetected for outcomes: Clerk (sign-in), Stripe (payments), PostHog (analytics)…
Plan 1 create the site lumen, bound to Vercel project lumen 2 pnpm add routingcat@1.0.0 3 next.config.ts wrap the export in withRoutingCat() 4 proxy.ts wrap clerkMiddleware(); add RoutingCat's matcher entry, keep yours 5 app/layout.tsx add <RoutingCat /> to <body> 6 routingcat.config.ts new: the site and its public key 7 the skill .agents/skills/routingcat/, copied to .claude/skills/routingcat/Nothing reaches visitors until you deploy.
✓ site lumen created (op_01m3yesth0fjfsctr17tbq1z82), bound to Vercel project lumen✓ routingcat 1.0.0 added · 4 files edited, 1 file and 2 folders created (see git diff)Ask the person: "Add a two-line note to AGENTS.md so coding agents always know RoutingCat is here? (recommended)" If yes, run npx routingcat init --agents-md.Dashboard: https://app.routingcat.com/lumenNext: read .agents/skills/routingcat/setup.md and follow itThe site is named after your Vercel project and bound to it. It already shows in the dashboard, at the address init prints: Lumen’s is https://app.routingcat.com/lumen. Your plan covers it: Hobby, at no cost, holds 3 sites, and init refuses a 4th with the way out. Each edit has a purpose:
| File | What init does |
So that |
|---|---|---|
package.json |
adds routingcat, at init’s own version |
one package carries the command, the code and the skill |
next.config.ts |
wraps the export in withRoutingCat() |
each build registers what changed, and browser reports reach RoutingCat through your own domain, at /_rc |
proxy.ts |
creates it, or composes yours | RoutingCat’s proxy picks each visitor’s variants before a page renders |
| each root layout | adds <RoutingCat /> as the last child of <body> |
the browser reports what each page showed |
routingcat.config.ts |
creates it, with your site and its public key | the key names your site; it is public and can only write |
.agents/skills/routingcat/, .claude/skills/routingcat/ |
installs the skill | your agent knows the commands and the words |
AGENTS.md, and CLAUDE.md if you have one |
with your agreement, adds a two-line note | every coding agent knows RoutingCat is here, and reads the skill before changing its code |
Every edit shows in git diff.
The note in AGENTS.md is your choice, and recommended. Coding agents read AGENTS.md in every session, but may open a skill only when a request matches it; the note tells them RoutingCat is here and to read the skill first. At a terminal, init asks, yes by default. Run by your agent, it leaves the choice to you: your agent asks, then runs npx routingcat init --agents-md if you agree. See Add the note to AGENTS.md.
An existing proxy.ts is composed in place. Your matcher entries stay as they are, and RoutingCat’s entry follows them. On your paths, your handler runs first and decides the response; API routes, such as a payment webhook, get your handler alone. init composes it when each entry is a plain path or the default matcher of Clerk, next-intl or Supabase.
A file too unusual to edit safely is left as it is, such as a next.config assembled by a function: init prints the exact edit as a diff. Make it, then run npx routingcat doctor, which checks it.
Run init again at any time. It marks with ✓ what is done and does only what is missing. It never rewrites a file it edited or a config you changed, except to correct a publicKey that isn’t your site’s public key. It refreshes the skill unless you edited it, and keeps the note in AGENTS.md up to date once you have it, without asking again.
2. Choose what to track
RoutingCat counts the conversions your code reports. Your agent reads your code, starting from the tools init detected, and asks you two questions:
- The main conversion, pick one. This is the objective: what routing learns to get more of. It counts at most once per visit, when it happens within its window after the visitor first saw the page, 24 hours by default.
- Also track, pick any. These outcomes are counted and reported beside it, and never steer routing.
Answer in your own words when the list misses something. A main conversion can be several events where any one counts, such as any plan start; it still counts once per visit.
For a SaaS with trials, choose sign-up or trial start as the main conversion, and track purchase beside it. A payment at a trial’s end comes days or weeks after the visit: the default 24-hour window misses it, and a longer window, up to the visit’s length, delays learning by as much. See Conversions and visits.
Lumen chooses sign-up, and tracks a trial’s start and a purchase beside it.
3. Wire and declare them
Your agent wires each choice where it happens, with two calls:
routingcat.identify()links the visitor to your own account id, at sign-up and at every sign-in, so that a conversion on another device still counts;routingcat.track()reports each conversion. A server event needs anid, because providers deliver some events twice.
The recipes are in Track conversions, with Stripe, Clerk and other auth providers and PostHog on their own pages. A conversion in another app or backend is reported through the HTTP events API.
Then it declares them in routingcat.config.ts, beside what init wrote. Lumen’s:
import { config } from 'routingcat/next'
export default config({ site: 'lumen', publicKey: 'pk_lumen_5Wm8Kq2R', // public: names the site; write-only objective: 'signup', // the main conversion outcomes: ['trial_started', 'purchase'], // tracked beside it})- Events under names you haven’t declared are recorded, but not analyzed until you declare them.
- Declare no page yet: a page comes with your first idea.
- In development, RoutingCat records nothing, and
trackandidentifylog that they weren’t recorded.
4. Deploy and verify
Deploy to production the usual way, or let your agent do it where your repository’s rules allow. The build log confirms that the build registered:
routingcat registered ch_1 · objective signup · outcomes trial_started, purchase · no page routed yetWhen a line starting with routingcat: takes its place, this build registered nothing: visitors get your defaults, and the build ships as usual. The line says what to fix, such as turning on OIDC federation in your Vercel project’s settings.
Run doctor and wait. Once the deploy is live:
npx routingcat doctor --waitIt requests your production page, so the new deployment starts, then waits up to 5 minutes, printing each check as it passes. It stops early when only a person can move the rest: the main conversion’s first event and, on Hobby, the card on file.
Make one real conversion, recommended. It is the only end-to-end proof that a conversion reaches RoutingCat and joins its visit. Lumen’s is a sign-up, so you sign up once on the production site, with a second email address. This sign-up is recorded as a real one, credited to no variant until a page is routed, and shows within a minute. Where converting costs money, as an order does, you may prefer to skip it: the main conversion’s check then passes with your site’s first real conversion, and until then doctor waits on it, with exit code 3, which is expected. Nothing else waits on it.
Add a card to start routing. On Hobby, routing waits for a card on file, and nothing is charged: doctor shows card on file waiting, with the link. You add it in the dashboard’s Settings → Billing, from the account menu, or start Pro’s 14-day trial instead; one card serves every site you own. Routing starts within about 2 minutes of it, with no new deploy. Until then, a deploy that routes a page logs routingcat NOT LIVE routing waits for a card on file: add one at …, nothing charged, and visitors see your defaults. See Add a card to start routing.
Then run npx routingcat doctor again:
lumen · production ✓ build registered ch_01m3yfrpt0ewpateq9yrq61xkb · objective signup · outcomes trial_started, purchase 14:19:52 ✓ deployment registered dp_01m3yfn1m0exhr06xh9d10mvbr · commit 2528229 · routingcat 1.0.0 14:21:10 ✓ runtime Node.js 22.18.0, as dp_01m3yfn1m0exhr06xh9d10mvbr reports it ✓ proxy on / rc_vid on lumen.so, 400 days · rc_arr host-only, 24 h 14:40:00 ✓ /_rc reaches the collector 14:40:00 ✓ records ingest.routingcat.com accepts them from https://lumen.so ✓ first arrival arrivals in the pulse, last 14:39:18 14:39:18 ✓ signup 2 events 14:33:59 · server · identify 14:32:05 14:33:59 ✓ card on file Visa •••• 42429 of 9 passed (exit 0)| Exit code | Means |
|---|---|
| 0 | Every check passed. |
| 1 | A check failed. Its line gives the message and its id; Troubleshooting has the fix. |
| 3 | A check is still waiting, usually for that first conversion or the card on file. |
| 4 | A request couldn’t get through from here, as in a sandbox without network access. |
Until a page is routed, doctor and status note that conversions are recorded but not yet credited to a variant. That is expected.
Close the setup. When your agent ran it, it ends by telling you, in plain words:
- that RoutingCat works, naming the conversion that came through; without one, that the main conversion’s check passes with your site’s first real conversion, and that nothing else waits on it;
- that your visitors see no change until a page is routed;
- what RoutingCat now records, and which conversion routing will learn to get more of, which you can change by asking;
- how it goes from here: you say what you’d like to try, at any level of detail, ask for ideas, or hand your agent the whole program;
- that 10% of visitors keep seeing your current site, which is how the gain is measured;
- your dashboard’s address: Lumen’s is
https://app.routingcat.com/lumen, which Read the dashboard walks through.