DEV Community

RAXXO Studios
RAXXO Studios

Posted on • Originally published at raxxo.shop

5 Vercel Edge Config A/B Tests That Lifted My Shopify CTR

  • Hero copy split lifted clicks 18% with a 12-line Middleware snippet and zero rebuilds

  • Geo flag for free shipping pulled checkout intent up 9% in DE/AT/CH only

  • Sticky CTA variant beat the static button 14% on mobile, no layout shift

  • Price display test (1.234,56€ vs 1,234.56€) won by 6% for European visitors

  • Kill switch on a flaky review widget recovered 4% of lost add-to-carts in under 60 seconds

I ship Shopify storefronts on Vercel and use Edge Config for every A/B test now. No rebuilds, sub-15ms reads at the edge, and toggles propagate in under a second worldwide. Here are the five test patterns I actually run, with the Middleware snippets I use and the lift each one produced.

If you want the full overview of why I picked this stack, see 5 Vercel Edge Config Patterns I Use For Shopify A/B Tests. This article goes one layer deeper into the actual experiments.

1. Hero Copy Split (lifted clicks 18%)

The first test you should ever run on a Shopify storefront via Edge Config is a hero copy split. Two headlines, one randomized cookie, zero rebuilds.

Edge Config schema:


{
  "hero_copy_test": {
    "enabled": true,
    "variants": {
      "A": "Build calmer Shopify stores.",
      "B": "Ship Shopify stores that load in 800ms."
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Middleware (middleware.ts):


import { get } from '@vercel/edge-config'
import { NextResponse } from 'next/server'

export async function middleware(req) {
  const test = await get('hero_copy_test')
  if (!test?.enabled) return NextResponse.next()
  const bucket = req.cookies.get('hero_ab')?.value
    || (Math.random() < 0.5 ? 'A' : 'B')
  const res = NextResponse.next()
  res.cookies.set('hero_ab', bucket, { maxAge: 60 * 60 * 24 * 30 })
  res.headers.set('x-hero-variant', bucket)
  return res
}

Enter fullscreen mode Exit fullscreen mode

The page reads x-hero-variant from the request headers and renders the right copy. I tag the variant on every PostHog event and let it run for 7 days.

In my run, variant B (the specific 800ms claim) beat the calm headline by 18% on click-through to /collections/all. Outcome over vibes wins almost every time.

Two notes on this pattern. First, the bucket lives in a cookie so the same visitor sees the same variant across navigations. Second, propagation time matters: when I flip enabled to false in the dashboard, every edge POP picks it up in under a second. That is the part client-side flag libraries cannot match because they ship a JS payload and run after hydration.

2. Geo-Aware Free Shipping Flag (9% checkout intent)

Free shipping converts, but global free shipping kills your shipping P&L. Edge Config plus the x-vercel-ip-country header gives you a per-country flag with one read.

Schema:


{
  "free_shipping_geo": {
    "DE": { "active": true, "threshold_eur": 0 },
    "AT": { "active": true, "threshold_eur": 0 },
    "CH": { "active": true, "threshold_eur": 80 },
    "US": { "active": false, "threshold_eur": 0 }
  }
}

Enter fullscreen mode Exit fullscreen mode

Middleware:


const country = req.geo?.country || req.headers.get('x-vercel-ip-country')
const cfg = await get('free_shipping_geo')
const flag = cfg?.[country]
if (flag?.active) {
  res.headers.set('x-free-ship', '1')
  res.headers.set('x-free-ship-threshold', String(flag.threshold_eur))
}

Enter fullscreen mode Exit fullscreen mode

The Shopify theme reads those headers via a Next.js route handler that proxies to the storefront, and the banner only renders for visitors in DE, AT, and CH. Checkout intent (proceed-to-checkout clicks) lifted 9% in those three markets while US and rest-of-world stayed flat. Edge Config let me kill the experiment for any country in one second if returns started climbing.

I also covered the broader pattern in 5 Cloudflare Workers Patterns I Use for Shopify Edge Logic if you're on Cloudflare instead of Vercel.

One thing to watch: req.geo is populated on the Edge runtime, but the x-vercel-ip-country header is the safer fallback in case you ever swap runtimes. Keep both lines. I also write the resolved country to a cookie so the page can render the banner immediately on the next navigation without re-reading headers.

3. Sticky CTA Variant (14% on mobile)

Mobile is where the money is and where the CTA gets lost. I tested a sticky bottom-bar Add to Cart against the static in-card button.

Schema:


{
  "sticky_cta": {
    "enabled": true,
    "split": 0.5,
    "label": "Add to bag",
    "min_viewport_px": 0,
    "max_viewport_px": 768
  }
}

Enter fullscreen mode Exit fullscreen mode

Middleware sets the bucket and a viewport-gated flag. The theme injects a 56px tall fixed bar that animates in after 400px of scroll. No layout shift because I reserve the height with padding-bottom: env(safe-area-inset-bottom, 56px) on `` only when the variant is active.

`javascript

const sticky = await get('sticky_cta')
if (sticky?.enabled) {
const bucket = Math.random() < sticky.split ? 'sticky' : 'static'
res.cookies.set('cta_ab', bucket, { maxAge: 86400 * 14 })
res.headers.set('x-cta-variant', bucket)
}

`

Lift: 14% more add-to-bag clicks on viewports under 768px, with no measurable hit to Largest Contentful Paint. I keep the sticky variant on for mobile and serve the static one to desktop where it already wins.

The reason I gate by viewport in Edge Config (instead of CSS media queries) is so I can ramp the experiment. Day one, I set split: 0.1 to see 10% of mobile visitors. Day three, if the numbers look healthy, I bump it to 0.5. Day seven, I either keep it at 0.5 or roll forward. No deploy needed, just a JSON edit.

4. Price Format Experiment (6% for EU visitors)

European number formatting matters more than people think. I tested 1.234,56€ (EU) against 1,234.56€ (US-with-Euro) for visitors from DE, AT, CH, NL, BE, and FR.

Schema:

`json

{
"price_format": {
"eu_visitors": "eu",
"default": "us",
"currencies": ["EUR"]
}
}

`

Middleware:

`javascript

const EU = new Set(['DE','AT','CH','NL','BE','FR'])
const fmt = await get('price_format')
const country = req.geo?.country || ''
const variant = EU.has(country) ? fmt.eu_visitors : fmt.default
res.headers.set('x-price-format', variant)

`

The product page formats prices server-side from the header. The EU format won by 6% on the add-to-bag click for EU traffic. Trust signal, plain and simple. The number looks native, the brain doesn't pause, the click follows.

Note: this is purely a display test. The cart and checkout always use Shopify's stored format, so accounting and tax stay clean.

One trap I hit early: I forgot to pass the format through to the cart drawer, which still showed the US-style number. The mismatch confused EU buyers and one of them emailed me about it. Fix was a single prop drilling pass, but it shows why you want a single source of truth for the variant header and let every component read from it.

5. Kill Switch on a Flaky Third-Party Widget (recovered 4% add-to-carts)

The most underrated A/B test is the one where B is "off". Third-party widgets (review apps, chat, popups) break in production. Edge Config gives you a kill switch you can flip from the dashboard without touching code.

Schema:

`json

{
"widgets": {
"reviews": { "enabled": true, "fallback": "static" },
"chat": { "enabled": true },
"popup": { "enabled": false }
}
}

`

The Shopify theme conditionally injects each widget's script tag based on a header set by the Middleware:

`javascript

const w = await get('widgets')
res.headers.set('x-widgets', JSON.stringify({
reviews: w?.reviews?.enabled ? 1 : 0,
chat: w?.chat?.enabled ? 1 : 0,
popup: w?.popup?.enabled ? 1 : 0,
}))

`

When the review widget started failing in production (third-party CDN had a bad day), I flipped reviews.enabled to false in Edge Config. The change hit every edge POP in under a second. The static fallback (server-rendered last 3 reviews) took over, and add-to-cart rate recovered 4% within minutes.

I now treat every third-party script as an experiment with a flag. If the script breaks, the flag goes off. No deploys, no panic. Edge Config is the closest thing to a real circuit breaker on a Shopify storefront.

Pair this with a tiny health-check route (/api/health/widgets) that hits each third-party endpoint every few minutes and flips the flag automatically if any of them return a non-200 for 3 checks in a row. I run this on a Vercel Cron. Self-healing storefronts are not a fantasy anymore. They take maybe 50 lines of code per widget.

Bottom Line

Edge Config replaced a 50 EUR/month LaunchDarkly bill and a slower client-side flag library on my Shopify projects. The five patterns above run all the time on a single store and cost roughly 5 EUR/month in Vercel bandwidth.

Pick one to start. Hero copy split is the lowest risk and highest learning per day. The kill switch is the highest insurance per minute. Once you have the Middleware wired, every new test is 10 lines of JSON and a deploy preview.

A few things I learned the hard way running these patterns side by side. Always namespace your keys (hero_copy_test, not hero), so two experiments don't fight for the same field when you delete or rename. Always log the variant on every analytics event so you can slice later. And never read more than 2 keys per request in Middleware: Edge Config is fast, but every read still adds 2-5ms. Group related flags into one object.

If you want the broader stack context, The 5 Vercel Cron Jobs That Keep My Studio Running pairs nicely. Crons for the writes, Edge Config for the reads. Together they cover most of what a solo Shopify operator needs from Vercel.

More patterns and write-ups in the RAXXO Lab if you want to keep going.

Top comments (0)