Handling redirects for unauthenticated users or rolling out A/B test variants in a Next.js app often leads to client-side bloat or security gaps. Middleware runs on every request before the page renders — on the server or at the edge. At Meteora Web, we use it in production to avoid client-side checks that slow down the experience and to ensure every visitor sees the right variant without extra round trips. This guide covers the two most common use cases: authentication with redirects and server-side A/B testing.
What Is Next.js Middleware and Why Use It for Auth and A/B Testing?
Middleware in Next.js is a function that runs for every incoming request before the route is rendered. You write it in middleware.ts at the project root, and it can modify the response (redirect, rewrite, add headers). It runs at the edge or on Node, depending on your setup.
For authentication, this means we can intercept requests to protected pages and redirect to login without the browser ever receiving the page contents. For A/B testing, we can read a cookie or parameter and route the user to a different version of the page, server-side. This keeps the bundle lean and business logic centralized.
Concrete scenario: one of our e-commerce clients wanted to show a new product page layout to 50% of visitors. With middleware, we set a cookie variant that determined an internal rewrite to /products/[id]?v=b (version B) without touching the component code. The result was a clean A/B test, trackable via GA4, with zero impact on page weight.
Who should use it
If you have a Next.js app with pages requiring login, or if you want to experiment with design variants without rewriting every component, middleware is the right tool. It’s not only for redirects: you can also inject data into requests (e.g., user ID) and use them in Server Components.
Sponsored Protocol
How to Configure Next.js Middleware to Redirect Unauthenticated Users?
The most frequent use case: a dashboard accessible only to logged-in users. If the user doesn’t have a valid token (e.g., JWT in a cookie), we redirect them to /login.
Step 1: Create the middleware.ts file
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session_token')?.value;
// Define protected routes
const protectedPaths = ['/dashboard', '/account', '/admin'];
const isProtected = protectedPaths.some((path) =>
request.nextUrl.pathname.startsWith(path)
);
// If protected and no token, redirect to login
if (isProtected && !token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
// Configure matcher to exclude static assets
export const config = {
matcher: ['/((?!_next/static|favicon.ico|api).*)'],
};
What it does: checks the session_token cookie. If missing and the route is protected, redirects to /login with the original path as ?redirect=... to bring the user back after login. The matcher excludes static assets to avoid unnecessary executions.
Sponsored Protocol
Step 2: Validate the token server-side (optional)
In production, a simple existence check isn’t enough. You should validate the JWT or call an authentication API. Since middleware runs on every request, external calls must be fast. We prefer using Edge Middleware with @clerk/nextjs or next-auth/middleware that already handle token validation with local caching.
Example with next-auth:
export { default } from 'next-auth/middleware';
export const config = { matcher: ['/dashboard', '/account'] };
Simpler, but only customizable via callbacks. If you need custom logic (e.g., only admin users), our getToken approach is more flexible.
Common mistake to avoid
Don’t put auth logic inside client components. We’ve seen projects where the token check happens in a useEffect after the page is already rendered — a flash of private content visible for a moment. Middleware prevents this: the request is blocked before the browser receives any HTML.
How to Implement Server-Side A/B Testing with Next.js Middleware?
Traditional A/B testing relies on client-side JavaScript or tag managers. Both have a flaw: the user sees the original version briefly before the script swaps the variant (Flash of Original Content — FOOC). With middleware, the variant is decided and applied before the response is sent.
Strategy: cookie or URL parameter
The middleware can read a cookie (e.g., ab_test_variant) or a URL parameter, and rewrite the path to a subfolder or add a header. We recommend using an internal rewrite to keep the URL clean.
Sponsored Protocol
Code for an A/B test on the homepage
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone();
// Only for the homepage
if (url.pathname === '/') {
// Read existing cookie or assign randomly
let variant = request.cookies.get('ab_variant')?.value;
if (!variant) {
variant = Math.random() < 0.5 ? 'a' : 'b';
const response = NextResponse.next();
response.cookies.set('ab_variant', variant, {
maxAge: 60 * 60 * 24 * 30, // 30 days
path: '/',
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
});
return response;
}
// If variant B, rewrite the homepage to /b
if (variant === 'b') {
url.pathname = '/b';
return NextResponse.rewrite(url);
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/'],
};
Here, the default homepage is variant A (/). Variant B is served from /b (which can be a different page or a set of components). The rewrite keeps the URL as / for the user, but Next.js renders the /b page. No visible redirect.
Tracking with Google Analytics 4
To measure results, send an experiment_view event client-side by reading the cookie:
// components/AbTracker.js
'use client';
import { useEffect } from 'react';
export default function AbTracker() {
useEffect(() => {
const variant = document.cookie.match(/(?:^|; )ab_variant=([^;]*)/);
if (variant) {
gtag('event', 'experiment_view', {
experiment_id: 'homepage_layout_v1',
variant: variant[1],
});
}
}, []);
return null;
}
Insert <AbTracker /> in the homepage layout.
Sponsored Protocol
Alternative: response header
If you prefer to handle logic server-side without cookies, you can add a response header and use a Server Component to decide which layout to show based on that header. In middleware:
const response = NextResponse.next();
response.headers.set('x-variant', variant);
return response;
Then in a Server Component, read the header with headers() from next/headers.
What Are the Best Practices to Avoid Ruining User Experience?
Middleware is powerful but must be used carefully. Here are the rules we follow in real projects.
1. Use a precise matcher, don't run on everything
The matcher reduces invocations. We set regex to include only the routes we need. Example: ['/dashboard/:path*', '/account/:path*', '/'] instead of a catch-all. On sites with hundreds of pages, this avoids unnecessary overhead.
2. Don't use middleware for slow API calls
If you need to validate a token against an external database, do it in an API Route, not in middleware. Next.js middleware does not support blocking async operations that take more than a few milliseconds. If necessary, use Edge Middleware with small libraries like jose to decode JWT locally.
3. Keep user state in cookies or JWT, not in global variables
Middleware has no access to server-side sessions (no persistent session). Use signed cookies or tokens. For A/B testing, a long-lived cookie is sufficient. For authentication, use next-auth which automatically manages session cookies.
Sponsored Protocol
4. Test redirects with search engine crawlers
If you redirect public pages (e.g., a test homepage), ensure crawlers always see the correct version and don't get stuck in redirect loops. For A/B testing, make the control variant always accessible and use the cookie only for human visitors.
What to Do Now
To immediately apply what you've learned:
- Add an auth middleware to your Next.js project. Create
middleware.tsat the root, define protected routes, and test the redirect to/login. - Implement a simple A/B test on the homepage with cookies and rewrites. Create two layout variants (e.g.,
app/page.tsxandapp/b/page.tsx) and verify that theab_variantcookie is set. - Check request logs to ensure the middleware does not introduce latency. Next.js shows execution time in the server log — it should be under 1 ms.
- Read the official documentation for deeper understanding: Next.js Middleware Docs.
- Explore our pillar article for a broader view on the Next.js App Router: Next.js App Router, Server Components and Data Fetching.
Middleware isn't just a shortcut — it's the right way to keep authentication and experiments server-side without bloating the frontend. We use it every day and see the benefits in performance and maintainability.