Serving Different Page Variants Before the Browser Loads
Client-side A/B testing has a well-known flaw: the page loads one version, then JavaScript swaps in the other. That causes layout shift, hurts Core Web Vitals, and creates a jarring experience. Vercel's approach for testing its redesigned Templates page was different — Edge Middleware runs before a request is served from the edge cache, so rewriting to a different variant happens with no client-side involvement.
The company used that capability to roll out a major redesign of its Templates marketplace in stages. The old page was a narrow list of framework starters; the new one is a full marketplace with categories, tags, and fuzzy search. Because the change touched one of Vercel's most visited pages, the launch was split into three phases: 20% of visitors saw the new variant during Early Access, a 50:50 split during Public Beta, and the new page for everyone at General Availability.
Routing Both Versions Through One Middleware
The new marketplace was built as an optional catch-all route, /templates/[[...slug]].tsx, in Next.js. That enabled statically generating pages for individual templates (e.g., /templates/next.js/nextjs-boilerplate) and category pages (e.g., /templates/blog) while using On-Demand ISR to add pages when new templates are published. The old marketplace was moved to /templates-old.tsx.
A middleware.ts file drove the experiment. On each new visit to /templates, it:
- Assigned the visitor to either the
oldornewvariant based on the current threshold (0.2 in the first phase). - Rewrote the user to
/templates-old.tsxif they were in the old cohort. - Stored the assignment in a
tm_varcookie so repeat visits got the same variant.
Handling Query Parameters Without Layout Shift
During development, Vercel ran into a data-fetching constraint. The new Templates page relied on query parameters to preserve filter state, but the pages were statically generated, and query parameters are not accessible inside getStaticProps. That forced data fetching to happen on the client.
For a request like vercel.com/templates?framework=svelte, the page would load empty and then populate — an invitation to layout shift. The team solved it by detecting query parameters in Edge Middleware. When parameters were present, the middleware rewrote the request to a special /templates/skeleton route that renders placeholder loaders while the client fetches the filtered data (via SWR) and then swaps in the real cards.
For the pre-generated landing pages that had no query parameters (e.g., /templates/svelte), no rewrite was needed. The content was already known at build time, so it was served fully static with no skeleton stage. Edge Middleware effectively provided a middle path: static HTML where possible, skeleton loaders only when user-specified filters required client-side data.
Iterating on Real-World Metrics
During Early Access and Public Beta, Vercel collected data from two analytics sources. Heap tracked conversion rates — how likely a user was to deploy a template after viewing the new versus old pages. Those measurements led to implementation tweaks that increased conversion by 16% with the new variant.
Algolia's Search Without Results reporting revealed another gap: more than 30 weekly searches for WordPress templates were returning nothing. Vercel added several WordPress templates ahead of the General Availability launch rather than letting that demand go unanswered.
The result is that the Templates page shipped with the confidence of a gradual rollout, and the mechanics of the test never degraded the user experience. Edge Middleware made the A/B test essentially invisible to users — no layout shift, no flash of the wrong version, no performance penalty. The tradeoff usually associated with client-side experimentation simply wasn't there.



