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

Last month, I spent about three days refactoring a messy data-fetching layer in a large Next.js application. We were passing userId and traceId through four layers of nested Server Components, eventually hitting a database utility that didn't even need the other props. It was a classic case of prop drilling, and it was brittle.
If you’ve been working with Server components vs client components: A practical guide, you know that Server Components don't have access to React Context. When you need to provide global request-scoped data—like an authenticated user object or a distributed tracing ID—without explicit prop passing, AsyncLocalStorage is your best friend.
Node.js provides AsyncLocalStorage to store data across asynchronous calls. Since each Next.js request runs in its own execution context, we can store request-specific information at the entry point and retrieve it anywhere down the call stack.
The primary benefit is decoupling. Your lower-level data services shouldn't care about the HTTP request or the middleware headers. They just want the userId.
First, we define a store module. I prefer a dedicated request-context.ts file to keep the logic isolated.
TYPESCRIPTimport { AsyncLocalStorage } from CE9178">'node:async_hooks'; export interface RequestStore { userId: string; traceId: string; } export const requestContext = new AsyncLocalStorage<RequestStore>();
Next, we need a way to initialize this. In Next.js, the best place to seed this data is usually a custom middleware or a layout component that wraps your providers. However, because we are in the Node.js runtime, we can hook into the request lifecycle.
To ensure we don't accidentally access the store outside of a request, we wrap the getter. This is where TypeScript Branded Types: Enforcing Domain Integrity at Compile-Time can help, but for a simple store, a strict getter function is usually sufficient.
TYPESCRIPTexport function getRequestContext(): RequestStore { const store = requestContext.getStore(); if (!store) { throw new Error(CE9178">'RequestContext accessed outside of request scope'); } return store; }
We first tried to use a global singleton pattern, but it failed immediately during concurrent requests. AsyncLocalStorage solves the concurrency issue by binding the store to the async execution flow.
However, there’s a catch. You must ensure that your code calling getRequestContext is only executed during the request-response cycle. If you try to trigger this inside a static generation step (like generateStaticParams), you'll hit that Error we defined above. It’s an intentional trade-off; it forces you to acknowledge that certain data is only available at runtime.
When you combine this with Next.js Server Actions: Implementing Type-Safe Mutations and Middleware, you get a very clean DX. You can have your server action call a service, which in turn calls getRequestContext() to fetch the userId, effectively "injecting" the dependency without the action signature becoming bloated.
run method.getRequestContext() in your deep service layers.RequestStore interface is strictly defined.This pattern makes testing significantly easier. You can mock the AsyncLocalStorage store in your unit tests without needing to spin up a full HTTP mock server for every single service-layer test.
I’m still not 100% sold on using this for everything. If the data is truly global and changes frequently, this is perfect. But if the data is only used by two components, keep it simple and pass it as a prop. Don't over-engineer your architecture just because you can.
I’ve also noticed that if you are using edge runtimes, AsyncLocalStorage behaves differently depending on the specific environment configuration. Always test your deployment pipeline to ensure the context propagation remains intact during streaming responses. It’s a powerful tool, but like any global state, it requires discipline to keep the codebase maintainable.
Master the Next.js App Router Data Provider pattern to decouple fetching from UI. Learn to inject data into React Server Components for cleaner, testable code.
Read more