AB Designer
DemoDemo experiment · cookie-free A/B at the edge, scored for significance on the week's exposures. Join the waitlist →
Edge A/B tests apply variants via HTML rewrite at the edge. Zero flash, no client JS.PRO
New experiment
Experiments
CTA button copyactive
/ · 50% to variant B · .hero-cta · trial_startedVariantExposuresConversionsRateLift
Control
1,830532.9%—bp < 0.05
1,831764.2%+43%Pricing headlineactive
/pricing · 50% to variant B · h1 · purchaseVariantExposuresConversionsRateLift
Control
893242.7%—b
892313.5%+29%Hero layoutpaused
/ · 50% to variant B · .heroVariantExposuresConversionsRateLift
Control
1,83000.0%—b
1,82900.0%—Edge integration
Deploy this Cloudflare Worker on your zone to apply variants at the edge. Set VERO_INGEST_SECRET as a Worker secret: the variant HTML is not public, so this uses your ingest secret and not the site token.export default {
async fetch(req, env, ctx) {
const res = await fetch(req);
if (!(res.headers.get('content-type') || '').includes('text/html')) return res;
const url = new URL(req.url);
// YOUR_INGEST_SECRET, not the public site token: this response carries the
// HTML of every running variant, and the token ships in your page source.
// Generate it in Site settings > Connect revenue, and keep it in a Worker secret.
const cfg = await fetch(
'https://api.getvero.dev/api/experiments/config',
{ headers: { authorization: 'Bearer ' + env.VERO_INGEST_SECRET }, cf: { cacheTtl: 60 } },
).then((r) => r.json()).catch(() => ({ experiments: [] }));
const exp = cfg.experiments.find((e) => e.path === url.pathname);
if (!exp) return res;
// cookieless sticky bucket from IP + UA
const id = (req.headers.get('cf-connecting-ip') || '') + (req.headers.get('user-agent') || '');
const bucket = [...id].reduce((a, c) => (a * 31 + c.charCodeAt(0)) % 100, 7);
const upTo = (i) => exp.variants.slice(0, i + 1).reduce((a, v) => a + v.weight, 0);
const variant = exp.variants.find((v, i) => upTo(i) > bucket) || exp.variants[0];
// Apply the variant AND stamp a marker so the Vero tracker's experiments
// module can record the exposure that matches what was actually served.
const rw = variant.changes.reduce(
(r, ch) => r.on(ch.selector, {
element(el) { el.setInnerContent(ch.html, { html: true }); },
}),
new HTMLRewriter().on('head', {
element(el) {
// Escaped, though both keys are constrained today (slugs and a fixed
// pair): this runs on the customer's own pages, so the day either one
// becomes free text it must not become stored XSS there.
const esc = (v) => String(v).replace(/[&<>"]/g, (ch) =>
({ '&': '&', '<': '<', '>': '>', '"': '"' })[ch]);
el.append('<meta name="vero-exp" content="' + esc(exp.key) + ':' + esc(variant.key) + '">', { html: true });
},
}),
);
return rw.transform(res);
},
};