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 21, 20264 min read

Next.js AsyncLocalStorage: Implementing Distributed Tracing in Server Actions

Master Next.js AsyncLocalStorage to enable cross-request tracing in Server Actions. Improve your observability with distributed logging in Next.js.

Next.jsAsyncLocalStorageObservabilityServer ActionsDistributed TracingNode.jsReactFrontendTypeScript
From above contemporary server cable trays without wires located in modern data center

Last month, I spent three days chasing a race condition in a production checkout flow that only appeared under heavy load. The logs were a mess—requests were interleaved, and I couldn't correlate a database query to the specific user action that triggered it. That’s when I realized that standard console logs are useless in the asynchronous, concurrent world of modern Next.js.

To fix this, I moved to a structured observability pattern using AsyncLocalStorage to inject a trace ID into every execution context.

Why Standard Logging Fails in Next.js

In a classic Node.js Express app, you have a clear request-response cycle. In Next.js, especially with Server Actions, your code execution often jumps between different parts of the framework—middleware, layouts, and components. If you just log a string, you lose the "who" and "why" of the request.

We initially tried passing a traceId as an argument to every single function in our service layer. It was a nightmare. Our function signatures became bloated, and we ended up with prop-drilling on the server side. It was brittle, and we inevitably forgot to pass the ID in one of our many utility functions.

Implementing Distributed Tracing with AsyncLocalStorage

Detailed close-up of gold CPU pins, showcasing technology and design on a circuit board.

AsyncLocalStorage is the secret sauce for solving this in Next.js. It allows you to store data that is accessible throughout the lifecycle of an asynchronous operation without explicitly passing it around.

Here is how I set up a simple context provider for our Server Actions:

TYPESCRIPT
// lib/trace-context.ts
import { AsyncLocalStorage } from CE9178">'async_hooks';

export interface TraceContext {
  traceId: string;
  userId?: string;
}

export const traceStore = new AsyncLocalStorage<TraceContext>();

To make this work, you need to wrap your request handler. In a Next.js environment, the best place to initialize this is inside your middleware or a dedicated base layer.

Wiring Up the Context in Middleware

You don't want to manually trigger this for every function. Instead, inject it early. Since Next.js middleware runs before the request hits the route handler, it’s the perfect place to generate a unique x-trace-id header if one doesn't exist.

After the request enters your application, you can use the Next.js AsyncLocalStorage: Type-Safe Request Context Injection pattern to ensure your store is populated.

Observability in Server Actions

When you move to Server Actions, you're dealing with POST requests that often bypass standard middleware logic. If you've been working on Next.js Server Actions: Implementing Type-Safe Mutations and Middleware, you know that standardizing your mutation layer is key.

Here’s how I wrapped a Server Action to ensure every log entry includes our trace ID:

TYPESCRIPT
// lib/logger.ts
import { traceStore } from CE9178">'./trace-context';

export const logger = {
  info: (message: string, meta?: object) => {
    const store = traceStore.getStore();
    console.log(JSON.stringify({
      message,
      traceId: store?.traceId,
      ...meta,
    }));
  }
};

By calling logger.info inside your Server Action, you get perfectly correlated logs in your logging aggregator (like Loki or Datadog). If you're running on Kubernetes, this is critical; you should check out Kubernetes Logging: Implementing Grafana Loki and Promtail to see how to ship these structured logs once you've successfully injected the trace ID.

The Trade-offs of AsyncLocalStorage

Is this a silver bullet? Not quite.

  1. Performance: Accessing AsyncLocalStorage is fast, but it isn't free. In extremely hot code paths, the overhead of context switching can add about 1-2ms to your execution time.
  2. Cold Starts: If you’re running on serverless functions, ensure your context initialization happens outside the handler function to avoid re-allocation.
  3. Complexity: Debugging AsyncLocalStorage issues can be tricky because the context is implicit. If you forget to wrap an async call in run(), your traceId will be undefined.

FAQ: Common Pitfalls

Q: Does this work with React Server Components? A: Yes, but keep in mind that RSCs are rendered on the server. As long as you initialize the store at the top level of your request cycle, the context will persist through the rendering of the component tree.

Q: Can I use this for global state? A: No. AsyncLocalStorage is for request-scoped data (like auth tokens or trace IDs). Do not use it for application-wide state management; stick to React Context or stores like Zustand for that.

Q: How do I handle external API calls? A: You must forward the traceId from your AsyncLocalStorage store to the headers of your fetch requests. This is how you maintain the trace across microservices, which is essential if you're using Kubernetes Observability: Implementing Distributed Tracing with Tempo for cross-service debugging.

Final Thoughts

Colorful confetti scattered over the word 'Finally' symbolizing celebration or achievement.

Implementing distributed tracing in Next.js isn't just about pretty dashboards. It's about knowing exactly what happened when a user reports a bug.

Next time, I might look into a more automated instrumentation approach using OpenTelemetry's SDK directly, rather than manual AsyncLocalStorage management. But for now, this approach has saved me countless hours of head-scratching during incident response. It’s messy, it’s manual, but it works—and in a production environment, that’s often the only metric that matters.

Back to Blog

Similar Posts

Steel framework cabinets housing servers networking devices and cables in contemporary equipped data center
Next.jsReactJune 21, 20264 min read

Next.js Multi-tenancy: Secure Data Isolation with AsyncLocalStorage

Achieve robust Next.js multi-tenancy by leveraging AsyncLocalStorage for secure, request-scoped data isolation across your Server Components and API routes.

Read more
Three syringes arranged on a red surface showcasing medical equipment with copy space.
Next.jsReactJune 21, 20263 min read

Next.js AsyncLocalStorage: Type-Safe Request Context Injection

Next.js AsyncLocalStorage enables global request context in Server Components. Learn how to implement type-safe dependency injection for auth and traceability.

Read more
Close-up of server racks in a data center highlighting modern technology infrastructure.
ReactNext.jsJune 21, 20264 min read

Next.js Server Actions: Implementing Type-Safe Mutations and Middleware

Next.js Server Actions can be brittle without the right guardrails. Learn to implement type-safe mutations, Zod validation, and middleware for production.

Read more