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 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.

Next.jsReactMulti-tenancyServer ComponentsAsyncLocalStorageSecurityFrontendTypeScript
Steel framework cabinets housing servers networking devices and cables in contemporary equipped data center

When I first started building a SaaS platform on the Next.js App Router, the biggest risk that kept me up at night was accidental data leakage between clients. We were using a shared-database approach, and the thought of a developer forgetting to append a tenant_id to a single Prisma query was enough to make me rethink our entire architecture.

If you're building a multi-tenant application, you can't rely on developers remembering to pass a tenantId through every single function call. You need a systemic, architectural guarantee that data fetching is scoped to the current tenant.

Why Request-Scoped Context Matters

In a standard Node.js environment, we often look for global state. But in a highly concurrent Next.js environment, global state is the enemy. We need a way to store the tenantId that is bound specifically to the current request lifecycle.

We initially tried passing the tenantId as a prop through every single Server Component. It worked, but it became a nightmare. Our components were bloated, and any shared utility function had to be updated to accept the ID. It was brittle and violated the DRY principle. As I wrote in Next.js AsyncLocalStorage: Type-Safe Request Context Injection, using AsyncLocalStorage is the standard way to solve this by providing a "global" variable that is actually scoped to the execution context of the request.

Implementing Multi-tenancy with AsyncLocalStorage

Interior view of contemporary multi-storey car park in Leiden, featuring unique design elements.

To get this working, we need to wrap our request lifecycle. In Next.js, this typically happens in the middleware.ts or a custom server-side layout. Here is how we set up the store:

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

export const tenantStorage = new AsyncLocalStorage<{ tenantId: string }>();

export function getTenantId(): string {
  const store = tenantStorage.getStore();
  if (!store) throw new Error("Tenant context not initialized");
  return store.tenantId;
}

Now, we need to populate this store. Since Next.js App Router doesn't provide a native "middleware context" that persists into Server Components, we use the AsyncLocalStorage pattern to bridge the gap. We wrap our root layout or a specific high-level component to ensure the context is available.

Preventing Leaks at the Data Layer

The real power of this pattern comes when you integrate it with your ORM. By creating a custom data-fetching wrapper, you can force every database call to include the tenantId retrieved from the AsyncLocalStorage.

TYPESCRIPT
// lib/db.ts
import { PrismaClient } from CE9178">'@prisma/client';
import { getTenantId } from CE9178">'./tenant-context';

const prisma = new PrismaClient();

export const tenantDb = {
  findMany: async (model: any, args: any) => {
    const tenantId = getTenantId();
    return model.findMany({
      ...args,
      where: { ...args.where, tenantId },
    });
  }
};

This prevents the "forgotten where clause" bug. If a developer tries to fetch data without the context, the getTenantId function throws an error, failing closed rather than leaking data. It’s a much safer approach than relying on manual filtering, which I discussed in the context of WordPress Multi-Tenancy: Secure Data Isolation for SaaS Plugins.

Trade-offs and Lessons Learned

Is this perfect? Not quite. One caveat is that AsyncLocalStorage is only available in Node.js environments. If you’re targeting Edge Runtime (like Vercel Edge functions), you might run into compatibility issues depending on your Node.js version. We've found that keeping our data-fetching logic inside standard Node.js Server Components avoids these pitfalls entirely.

We also had to be careful about how we handle fetch requests to external APIs. If your backend is a microservice, you need to ensure that the tenantId is injected into the headers of those outgoing requests as well. It’s easy to focus on the database and forget that your external service calls need the same level of isolation.

When comparing React Server Components vs Client Components in Next.js, remember that AsyncLocalStorage is strictly server-side. Never attempt to pass this context to the client. Keep the boundary clean: the server handles the isolation, and the client just receives the filtered data.

Final Thoughts

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

Building secure Multi-tenancy in Next.js requires a shift in how you think about your data layer. By moving away from prop-drilling and toward request-scoped context, you reduce the surface area for bugs significantly.

I’m still experimenting with how to better handle nested tenant hierarchies—where a user might belong to multiple workspaces. For now, the AsyncLocalStorage approach provides about 95% of the security we need with minimal overhead. It’s a pattern that has saved us from several potential incidents during our last refactor.

Back to Blog

Similar Posts

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
From above contemporary server cable trays without wires located in modern data center
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.

Read more
View of large industrial pipelines running through a lush forest landscape.
ReactNext.jsJune 21, 20264 min read

Next.js Server Actions: Building Resilient Pipelines with Zod

Next.js Server Actions require robust patterns for success. Learn to implement Zod validation, optimistic UI updates, and type-safe mutations for better UX.

Read more