Eliminate Prop Drilling in Next.js with AsyncLocalStorage

This article explains how prop drilling in Next.js applications can be avoided by using Node.js's AsyncLocalStorage to store per‑request context such as requestId, tenantId, and userId, providing code examples, implementation steps, and practical usage tips for clean, maintainable backend logic.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Eliminate Prop Drilling in Next.js with AsyncLocalStorage

Next.js is indeed an excellent framework, but it has some unfriendly aspects, especially prop drilling.

If you are tired of prop drilling or constantly passing the same data across multiple layers, you will love this tool.

🚧 Prop Drilling Problem

In typical Next.js applications, especially those using the App Router and server components, you often need to access shared context such as userId, tenantId, requestId, or feature flags.

When a requestId is generated in a server‑side function and you want to propagate it to all backend API calls, you must explicitly pass that field.

You end up passing the same value from one function to another through routers, services, loggers, and possibly all the way to database calls.

This is called Prop Drilling , which is especially annoying in backend logic where you don’t want business code cluttered with boilerplate context passing.

💡 Solution: AsyncLocalStorage

Node.js (since v13.10) provides a useful tool called AsyncLocalStorage. It allows you to store per‑request state that can be accessed anywhere in the request’s lifecycle without passing values.

It is somewhat like a per‑request globalThis, but it is scope‑isolated and safe for asynchronous calls.

Here is a simple example:

import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

function logWithContext(message: string) {
  const store = storage.getStore();
  console.log(`[${store?.requestId ?? 'unknown'}] ${message}`);
}

storage.run({ requestId: 'abc123' }, () => {
  logWithContext('User fetched data');
});

🔥 Amazing! You no longer need to pass requestId —it is available exactly where you need it.

🧪 Real‑World Example: Next.js API Route Handler

Suppose you are building a Next.js app with API routes under app/api and you want each request to have a unique requestId that can be used in logs, database calls, etc.

Step 1: Set Up Storage

import { AsyncLocalStorage } from 'node:async_hooks';

type Context = {
  requestId: string;
  userId?: string;
};

export const asyncContext = new AsyncLocalStorage<Context>();

Step 2: Wrap the Route Handler

import { asyncContext } from '@/lib/async-context';
import { v4 as uuid } from 'uuid';
import { logWithContext } from '@/lib/logger';

export async function GET() {
  const requestId = uuid();
  return asyncContext.run({ requestId }, async () => {
    logWithContext('Handling request to /api/hello');
    return new Response(JSON.stringify({ hello: 'world' }));
  });
}

Step 3: Use the Context Anywhere

import { asyncContext } from './async-context';

export function logWithContext(message: string) {
  const store = asyncContext.getStore();
  const tag = store?.requestId || 'unknown';
  console.log(`[${tag}] ${message}`);
}

No need to pass requestId around; just plug it in where needed.

🛠️ Using It in Practice

When guiding a junior developer, you can suggest using AsyncLocalStorage in the following scenarios:

Logging each request’s context (e.g., requestId)

Tagging tenant information in database queries ( tenantId)

Sharing user identifiers ( userId) across multiple asynchronous service layers

Accessing feature flags or locale in deeply nested logic

This keeps your logic clear and prevents code from becoming tangled—a subtle but powerful “advanced developer” technique.

⚠️ Caveats

Works only in the Node.js runtime; not applicable to Edge functions or middleware.ts.

Not suitable for long‑running background tasks; it only persists data within a request’s lifecycle.

May not persist across certain asynchronous boundaries (e.g., detached callbacks) unless handled carefully.

💬 Final Thoughts

Honestly, this pattern has saved me a lot of trouble. It isn’t as flashy as some cutting‑edge React tricks, but it solves a very practical problem in an elegant way—exactly the kind of solution you discover when working on production systems.

If you are building scalable Next.js and Node.js applications with complex API logic, add AsyncLocalStorage to your toolbox.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend DevelopmentNode.jsNext.jsAsyncLocalStorageProp Drilling
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.