Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Next.jsReactJune 22, 20264 min read

Next.js Traffic Shadowing: Architecting Canary Releases at the Edge

Master Next.js traffic shadowing to safely deploy Canary releases. Learn how to use Edge Middleware to mirror requests for testing Server Components at scale.

Next.jsServer ComponentsCanary DeploymentsTraffic ShadowingEdge MiddlewarePerformanceArchitectureReactFrontendTypeScript

During a recent migration of our core checkout flow, we hit a wall: our unit tests passed, but the actual rendering performance of our new Next.js Server Components varied wildly under real-world load. We needed a way to validate the new implementation against production traffic without risking the user experience. That’s when we turned to traffic shadowing.

By leveraging Next.js Edge Middleware, we successfully mirrored requests to a secondary "canary" deployment. This allowed us to compare results in real-time, effectively performing dark launches for our most critical server-side logic.

The Problem with Traditional A/B Testing

Usually, A/B testing happens at the client level. You flip a flag, the browser hits a different endpoint, and you track the conversion. But with Server Components, the heavy lifting happens before the client even sees a byte of HTML. If your data fetching logic is flawed, the user gets a slow page or a 500 error before your client-side A/B test even triggers.

We needed a way to test the server-side pipeline itself. We considered simple feature flags, but that didn't help us monitor the performance delta between the stable and canary versions of our components. We needed to see both versions execute for the same set of incoming requests.

Implementing Traffic Shadowing via Edge Middleware

The strategy is simple: use a non-blocking request to mirror the incoming traffic to a secondary service. Since we’re already optimizing our pipelines for Next.js Server Components: Architecting Resilient Data Fetching Pipelines, we wanted to keep the overhead minimal.

In our middleware.ts, we intercept the incoming request. If the request meets our criteria (e.g., a specific path or user segment), we fire a background fetch to our canary environment.

TYPESCRIPT
// middleware.ts
import { NextResponse } from CE9178">'next/server';
import type { NextRequest } from CE9178">'next/server';

export async function middleware(request: NextRequest) {
  const url = request.nextUrl.clone();
  
  // Shadowing logic
  if (process.env.ENABLE_SHADOWING === CE9178">'true') {
    const shadowUrl = new URL(url);
    shadowUrl.hostname = CE9178">'canary-api.myapp.com';
    
    // We fire-and-forget the shadow request
    fetch(shadowUrl, {
      method: request.method,
      headers: request.headers,
      body: request.body,
    }).catch((err) => console.error(CE9178">'Shadow request failed', err));
  }

  return NextResponse.next();
}

Why this works for Canary Deployments

The beauty of this approach is that the primary response remains unaffected. Even if the shadow request takes an extra 300ms or fails entirely, the user never knows. We use this "dark" path to log the response times and payload differences between our stable and experimental builds.

When we combine this with Next.js Request Hedging: Reducing Tail Latency with Speculative Execution, we can even gain insights into how the new code handles p99 spikes. It’s essentially a way to stress-test your code with real production inputs.

The Trade-offs of Edge-Routed Traffic Shadowing

It isn't a silver bullet. There are three major pitfalls we encountered:

  1. Authentication: You cannot simply pass the Authorization header. If your canary environment validates tokens against a session store, you might trigger false negatives. We had to create a "shadow-only" service account to bypass strict session checks.
  2. Side Effects: If your shadowed request performs a POST or PATCH operation, you risk double-processing data. We strictly limited our shadowing to GET requests for data fetching components.
  3. Cost: Doubling your traffic means doubling your server costs. We capped the shadowing to only 5% of our total traffic, which was enough to gather statistically significant data without blowing our AWS budget.

Scaling the Strategy

As we matured, we moved beyond basic shadowing. We started using Edge Middleware to inject custom headers into the shadow request. This allows our canary environment to log the specific version ID being tested, making it trivial to query our observability platform for "Version A vs Version B" performance metrics.

If you're dealing with complex data dependencies, ensure you've already implemented Next.js Request Memoization: Stop Over-Fetching in Server Components. Without proper memoization, your shadow requests will likely trigger redundant database calls, skewing your performance data and potentially hitting rate limits on your internal APIs.

FAQ: Common Concerns

Does this increase latency? If you await the shadow request, yes. Always use a fire-and-forget pattern. In Vercel’s runtime, the fetch will continue executing even after the middleware returns the response to the client.

How do I handle sensitive user data? Strip PII (Personally Identifiable Information) in the middleware before forwarding the request. We scrub headers like Cookie and Authorization unless they are strictly necessary for the canary to function.

Is this overkill for small teams? If you're shipping features once a week, maybe. But if you're managing complex Server Component trees, the peace of mind is worth the roughly two days of setup time.

Final Thoughts

Traffic shadowing is a powerful tool, but it requires discipline. We still find ourselves tweaking the sampling rates to avoid hitting our backend database limits. Next time, I’d probably look into using a dedicated service mesh for the request mirroring rather than doing it in Middleware, as it would offload the logic from our main Next.js runtime. Still, for a pure frontend/full-stack team, this approach is hard to beat for visibility.

Back to Blog

Similar Posts

ReactNext.jsJune 22, 20264 min read

Next.js Server Components: Architecting Resilient Data Fetching Pipelines

Next.js Server Components require robust data fetching strategies. Learn how to use AsyncLocalStorage and request-scoped caching to build resilient architectures.

Read more
Next.jsReact
June 22, 2026
4 min read

Next.js Request Deduplication: Architecting Global Coalescing Proxies

Next.js request deduplication is critical for production apps. Learn how to architect global coalescing proxies to prevent redundant fetches in Server Components.

Read more
Next.jsReactJune 22, 20264 min read

Next.js Circuit Breaker Pattern: Building Resilient Server Actions

Next.js Server Components often face cascading failures. Learn to implement the Circuit Breaker pattern to protect your app and ensure high fault tolerance.

Read more