---
title: "How to A/B Test a Next.js Landing Page Without Middleware Conflicts"
description: "Middleware-based A/B testing breaks when it has to share the file with auth, redirects, and i18n logic. Here's how to split traffic without adding to middleware.ts at all."
canonical: https://landtest.dev/blog/ab-test-nextjs-landing-page-without-middleware-conflicts
last-updated: 2026-10-30
---

# How to A/B Test a Next.js Landing Page Without Middleware Conflicts

> Middleware-based A/B testing breaks when it has to share the file with auth, redirects, and i18n logic. Here's how to split traffic without adding to middleware.ts at all.

You can A/B test a Next.js landing page without touching `middleware.ts` by splitting traffic at the hosting/deploy layer instead of inside the request pipeline — either with a platform-native split (Vercel's rewrite rules, a reverse proxy config) or with a tool that ships the variant as its own route and lets your existing middleware run unmodified. The conflict most teams hit isn't Next.js itself, it's that `middleware.ts` only runs once per request, and every library that needs request-time logic — Clerk or NextAuth for auth, `next-intl` for locale routing, an A/B test rewrite — has to share that one file and its execution order.

## Why the conflict happens

Next.js middleware executes as a single function per matched path. If you're already using Clerk's `authMiddleware` or NextAuth's `withAuth`, and you add an A/B test library that also wants to intercept the request (to rewrite `/pricing` to `/pricing-variant-b` based on a cookie), you're now composing two middleware functions by hand. Get the order wrong — auth check running after the rewrite, or the rewrite stripping headers the auth check needs — and you get intermittent redirect loops or a variant served to logged-in users who should never see it.

[Next.js's own GitHub discussion on static pages and middleware](https://github.com/vercel/next.js/discussions/33511) covers a related issue: middleware rewrites can affect CDN cache-control headers, which means an A/B test rewrite can accidentally make your CDN cache one variant and serve it to everyone.

## Option 1: Keep the split in middleware, but isolate it

If you're committed to a middleware-based split (GrowthBook, a custom cookie-bucket function, Vercel Edge Config), the fix is composition, not avoidance. Put the A/B logic in its own function, run auth first, and make the rewrite a no-op for any already-authenticated or already-bucketed request:

```ts
// middleware.ts
import { authMiddleware } from "@clerk/nextjs";
import { assignVariant } from "./lib/experiment";

export default authMiddleware({
  afterAuth(auth, req) {
    if (auth.userId) return; // skip the test for logged-in users
    return assignVariant(req);
  },
});
```

This works, but it means your experiment library's config, cookie names, and bucketing logic now live permanently in the file that also gates your app's auth. When the test ends, cleaning that back out is a manual diff against a file that's also under active development for unrelated reasons.

## Option 2: Split outside the request path entirely

The alternative is to not add anything to `middleware.ts` at all. Landtest generates the variant as a real branch in your repo and deploys it to its own preview URL through your existing CI/CD, then the traffic split happens at the hosting layer (Vercel rewrite rules, your reverse proxy, or DNS-level weighting depending on your host) rather than inside a Next.js request handler your app code owns. Your `middleware.ts` file never changes for the test, and when you declare a winner, the split configuration is removed along with the losing branch — nothing to manually unwind from your auth logic.

This matters most if you're on Clerk, since [Clerk's middleware also needs to run on every matched route](https://clerk.com/docs/references/nextjs/clerk-middleware) and composing a second middleware concern on top of it is exactly the kind of thing that's easy to get right in testing and wrong in production three weeks later when someone edits the auth rules and doesn't remember the A/B rewrite is downstream of it.

## Which option to pick

If you're already comfortable owning middleware composition and want full control over the bucketing algorithm, Option 1 is fine — GrowthBook and a hand-rolled cookie split both work this way. If you'd rather your auth middleware stay exactly as simple as it is today and have the split live entirely outside your request code, that's the gap Landtest is built to close.

## FAQ

**Does Clerk conflict with A/B testing middleware?**
Not inherently, but both want to run on the same requests, so you have to compose them deliberately — run auth first, and make sure the A/B rewrite doesn't fire for authenticated users unless that's intended. See Option 1 above.

**Can I A/B test without editing middleware.ts at all?**
Yes, if the split happens at the hosting or deploy layer instead of inside a Next.js request handler. That's how Landtest runs tests — see [github-native A/B testing](/ab-testing).

**Will an A/B test rewrite break my CDN caching?**
It can. A middleware rewrite based on a cookie can cause a CDN to cache one variant and serve it to all visitors regardless of assignment, since the cache key doesn't account for the cookie. Splitting at the edge/host level with cache-aware rules avoids this.

---

Related: [Best A/B testing tools for Next.js developers](/blog/best-ab-testing-tools-nextjs-developers) · [GrowthBook alternatives for Next.js developers](/blog/growthbook-alternatives-nextjs) · [Landtest for A/B testing](/ab-testing)
