Set up RoutingCat
This tutorial takes a Nuxt 4 site on Vercel, Cloudflare Workers or Cloudflare Pages 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 that signs its users in with Clerk. What your site needs lists the requirements.
1. Install with init
In your Nuxt app’s folder, you or your agent run:
npx routingcat initIt checks your project first, and stops before changing anything when something needs you:
- Nuxt 4.6 or later, installed. Upgrading is your call; the message gives Nuxt’s upgrade command.
- Where the site deploys, as the folder says: a Vercel link or
vercel.json, a Wrangler configuration, or the Nitro preset that yournuxt.config, your environment or your build script names. Where nothing names a host, or two things name different ones,initasks at a terminal; run by an agent, it stops, and you run it again with--host vercel,--host cloudflare-workersor--host cloudflare-pages. - On Vercel, a folder linked to a Vercel project, and the Vercel CLI signed in: run
npx vercel linkornpx vercel login, theninitagain. - On Cloudflare, Wrangler signed in, or the account named by
account_idin the Wrangler configuration or byCLOUDFLARE_ACCOUNT_ID(the message). Signing in to Cloudflare needs you, not your agent: runnpx wrangler login, theninitagain. - On Cloudflare, your host’s Nitro preset, whose build writes the Worker that RoutingCat’s wraps:
cloudflare_moduleon Workers,cloudflare_pageson Pages. Where yournuxt.config, your environment or your build script names another,initstops, since nothing of RoutingCat’s would run before your pages: name your host’s preset there instead. On Workers, the Wrangler configuration also leaves Workers Cache off for that Worker (the message). - 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 on Vercel, run by an agent and signed in already:
routingcat 1.0.0 · <project>
Checks ✓ Nuxt 4.6.0 · pnpm ✓ Vercel project lumen · Vercel CLI signed in as emma ✓ no middleware.ts yet: init writes one ✓ signed in to RoutingCatDetected for outcomes: Clerk (sign-in)
Plan 1 create the site lumen, bound to Vercel project lumen 2 pnpm add routingcat@1.0.0 3 nuxt.config.ts add routingcat/nuxt to modules 4 middleware.ts new: middleware() from routingcat/vercel on Node.js, and RoutingCat's matcher 5 .gitignore add .routingcat/, where the build writes RoutingCat's credential 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 · 3 files edited, 2 files 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 itOn Workers, the plan wraps Nitro’s Worker in RoutingCat’s instead of writing a middleware, and init ends with a note about Cloudflare and the step that gives your builds their credential:
routingcat 1.0.0 · <project>
Checks ✓ Nuxt 4.6.0 · pnpm ✓ production address https://lumen.so, from the routes in wrangler.jsonc ✓ Cloudflare Workers project lumen · account Lumen · Wrangler signed in as emma@lumen.so ✓ no worker.ts yet: init writes one ✓ wrangler.jsonc: init sets main worker.ts, the nodejs_compat flag, run_worker_first and the version metadata binding CF_VERSION_METADATA ✓ signed in to RoutingCatDetected for outcomes: Clerk (sign-in)
Plan 1 create the site lumen, bound to Cloudflare Workers project lumen 2 pnpm add routingcat@1.0.0 3 nuxt.config.ts add routingcat/nuxt to modules 4 worker.ts new: worker() from routingcat/cloudflare, which decides before the page 5 wrangler.jsonc set main worker.ts, the nodejs_compat flag, run_worker_first and the version metadata binding CF_VERSION_METADATA 6 .gitignore add .routingcat/, where the build writes RoutingCat's credential 7 routingcat.config.ts new: the site and its public key 8 the skill .agents/skills/routingcat/, copied to .claude/skills/routingcat/Nothing reaches visitors until you deploy.
✓ site lumen created (op_01m3yesth0fjfsctr17tbq1z82), bound to Cloudflare Workers project lumen✓ routingcat 1.0.0 added · 4 files edited, 2 files and 2 folders created (see git diff)· RoutingCat needs Workers Paid: on the Free plan, past 100,000 requests a day, every page the Worker runs first on answers 429 until midnight UTC.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: ask the site's owner to run npx routingcat keys deploy --branch <your production branch> in their own terminal, and to set what it prints as ROUTINGCAT_DEPLOY_KEY in Cloudflare's build variables; read .agents/skills/routingcat/setup.md and follow itCloudflare explains the note, with the other settings of your Cloudflare account and zone. The deploy key is step 4.
The site is named after your Vercel project, or on Cloudflare after the name in your Wrangler configuration, and bound to that project. Without a Wrangler name, as on Pages, the site and its binding take your folder’s name. 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 |
nuxt.config.ts |
adds routingcat/nuxt to modules; on Cloudflare, also sets nitro.preset to cloudflare_module or cloudflare_pages where nothing names a preset |
each build registers what changed, and every page carries the head snippet, which shows each visitor’s variants before the page paints |
middleware.ts, on Vercel |
creates it, or composes yours: RoutingCat’s middleware, on Node.js, with its matcher | RoutingCat picks each visitor’s variants before Vercel serves the page, from its cache or not, and browser reports reach RoutingCat through your own domain, at /_rc |
worker.ts and wrangler.jsonc, on Workers |
creates the Worker that wraps Nitro’s, and sets the Wrangler configuration’s main, the nodejs_compat flag, run_worker_first and the version metadata binding |
the same on Cloudflare: every request but those for Nuxt’s built assets reaches RoutingCat’s Worker first, and each version of the Worker registers under its own id |
.gitignore |
adds .routingcat/ |
each build writes there the credential your deployment runs with, which stays out of your repository |
routingcat.config.ts, or .mjs without TypeScript |
creates it, with your site and its public key; on Cloudflare, also your production address, as origin |
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 |
On Pages, init writes no entry of its own: each build wraps the Worker that Nitro writes for Pages, through the module. Every edit shows in git diff.
On Cloudflare, routingcat.config.ts names your production address. Your builds tell RoutingCat the address from its origin, and doctor and preview links open your pages there. init takes it from --origin, else from your Wrangler routes and custom domains, and prints a ✓ production address line that says where it found it. Where neither gives it, as is usual on Pages, init asks you for it at a terminal. Run by an agent, it stops instead, with exit code 2: you or your agent run it again with your address, npx routingcat init --origin https://lumen.so.
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.
On Vercel, an existing middleware.ts is composed in place. Your matcher entries stay as they are, and RoutingCat’s follow them. On your paths, your handler runs first and decides the response. RoutingCat’s entries cover your pages and /_rc, and leave out Nuxt’s assets, _ipx images, API routes under /api and any path with a file extension, payloads included.
On Workers, a Worker of your own stays your Wrangler main. init prints the wrap to make around it, with the Wrangler edit that goes with it, and makes neither. A main that names Nitro’s own Worker is replaced by worker.ts.
A file too unusual to edit safely is left as it is, and init prints the edit to make: as a diff where it can, else in words. For a nuxt.config whose exported config it can’t read, it says to add routingcat/nuxt to the modules of the config that file exports. Make the edit, 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, and on Cloudflare to add your production address to a config that names none. 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 its iPhone waitlist beside it. Its trials and payments are Stripe’s, which RoutingCat doesn’t count on a Nuxt site yet, so they stay untracked rather than sent as your server’s own events.
3. Wire and declare them
Your agent wires each choice where it happens, with two calls in your server code, where RoutingCat’s module makes routingcat available with no import:
routingcat.identify(event, accountId)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(event, name, options)reports each conversion. A server event needs anid, because providers deliver some events twice.
The recipes are in Track conversions, with 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. The example app’s declares one main conversion, joining its waitlist:
import { config } from 'routingcat/vue'
export default config({ site: 'routingcat-nuxt-example', publicKey: 'pk_routingcat-nuxt-example_Hq4Zr8Tw', // public: names the site; write-only objective: 'waitlist', // the main conversion: joining the waitlist outcomes: ['contact_sales'], // tracked beside it: "Talk to us" on the about page, from the browser})Lumen’s names signup as its objective, and waitlist in its outcomes, the event 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
On Cloudflare, give your builds the deploy key first. Cloudflare’s builds prove themselves to RoutingCat with your site’s deploy key, which the site’s owner mints, naming your production branch, here main:
npx routingcat keys deploy --branch mainIt shows the key once: set it as ROUTINGCAT_DEPLOY_KEY in your Cloudflare project’s build variables. It proves production only for builds of that branch, as their CI reports the branch. Access and keys says what the key allows and how to replace it.
On Cloudflare, to deploy from your machine instead, with nuxt build then Wrangler, run the build with ROUTINGCAT_ENV=production: it registers under your sign-in once you confirm its plan, as Deploy from your machine describes. Without it, a build on your machine registers nothing.
Deploy to production the usual way, or let your agent do it where your repository’s rules allow: on Vercel, a production deployment; on Cloudflare, a build of your production branch. The build log confirms that the build registered:
routingcat registered ch_01m3yfrpt0fbttmb64x6008p1n · objective signup · outcomes waitlist · 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: on Vercel, often turning on OIDC federation in your Vercel project’s settings; on Cloudflare, the deploy key.
Run doctor and wait. Once the deploy is live:
npx routingcat doctor --waitIt requests your production page, so the new deployment starts and registers, 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. On Vercel:
$ routingcat doctorlumen · production ✓ build registered ch_01m3yfrpt0ewpateq9yrq61xkb · objective signup · outcomes waitlist 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 ✓ middleware on / rc_vid on lumen.so, 400 days 14:40:00 ✓ /_rc reaches the collector 14:40:00 ✓ head snippet 1 page carries it ✓ policy lets RoutingCat run, apply and record ✓ 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 •••• 4242 · AGENTS.md has no RoutingCat note, so coding agents may skip the skill unless a request matches it; if the person wants the note, run npx routingcat init --agents-md11 of 11 passed (exit 0)On Cloudflare, Worker on / replaces middleware on /, and runtime is Vercel’s alone; on Workers, Worker version checks the version each deployment registers under. A line marked · is information and needs nothing: here, that AGENTS.md has no note for coding agents, which is your choice. Troubleshooting explains each check.
| 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.