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
ReactNext.jsJune 23, 20264 min read

Next.js Request Memoization: Using React Cache and AsyncLocalStorage

Master Next.js request memoization with React cache and AsyncLocalStorage. Learn to stop redundant data fetching and optimize your Server Components today.

Next.jsReactPerformanceServer ComponentsCachingFrontendTypeScript

Last month, I spent three days debugging a dashboard that was hitting our internal API roughly 40 times on a single page load. The culprit wasn't bad logic; it was a deeply nested tree of Server Components, each independently requesting the same user profile and permissions data.

In a traditional SPA, you'd reach for TanStack Query or SWR. In the App Router, those tools don't quite fit the server-side lifecycle. If you're building production-grade Next.js applications, you need to master request-scoped memoization to keep your server-side performance predictable.

The Problem: The N+1 Query Nightmare

When you pass data down through props, you create a strict coupling between parent and child components. Sometimes, that's fine. But when your UI is highly modular, you often end up with leaf components that need access to global context—like the current session, active locale, or feature flags.

If you fetch this data inside every leaf component, you're triggering redundant network requests. Even with HTTP/2 multiplexing, you're wasting CPU cycles on the server, increasing latency, and hammering your database. We initially tried passing a "data object" as a context provider, but that forced us to fetch everything at the root, which hurt our time-to-first-byte (TTFB). We needed something that felt like global state but lived only for the duration of a single request.

Leveraging React Cache for Deduplication

The simplest way to handle this in Next.js is the cache function from the react package. It wraps a function and returns a version that memoizes the result based on the arguments passed to it.

JAVASCRIPT
import { cache } from CE9178">'react';

export const getUser = cache(async (id) => {
  const res = await db.user.findUnique({ where: { id } });
  return res;
});

When you call this inside a Server Component, React ensures that if getUser is invoked with the same id during the render pass, it returns the cached promise rather than firing a new request. It’s clean, it’s native, and it’s specifically designed for Next.js.

However, cache has a limitation: it only works if you share the exact same function reference across your component tree. If you're building a library or a complex data layer, you need a more robust way to handle context.

Scaling with AsyncLocalStorage

When cache isn't enough—for instance, when you need to store request-specific headers or transaction IDs that aren't arguments to a function—AsyncLocalStorage is your best friend. It allows you to track state across asynchronous calls within a single request execution context.

I use it to create a "Request Store." Here’s how I typically set it up:

JAVASCRIPT
import { AsyncLocalStorage } from CE9178">'node:async_hooks';

const requestStore = new AsyncLocalStorage();

export function getRequestData() {
  return requestStore.getStore();
}

export function runWithRequest(data, callback) {
  return requestStore.run(data, callback);
}

By wrapping your entry point—usually in a layout or a custom middleware-like pattern—you can inject a store that any component deep in the tree can access. This is particularly useful for Next.js apps that need to maintain state across complex data pipelines without prop-drilling or redundant fetches.

The Trade-offs of Memoization

While these patterns are powerful, they aren't free.

  1. Memory Pressure: If you cache large objects, you're holding that memory for the duration of the request.
  2. Staleness: Because this is request-scoped, you don't have to worry about long-term cache invalidation, but you do have to be careful about ordering. Always trigger your primary data fetches as high as possible if they are shared.
  3. Complexity: Debugging memoized functions can be tricky. If a value isn't updating as expected, you often have to clear the cache or inspect the AsyncLocalStorage store directly.

Putting It All Together

In my current stack, I use cache for pure data fetching (like database queries) and AsyncLocalStorage for request-scoped metadata (like user roles or current tenant IDs). This split keeps the code readable while preventing the dreaded "waterfall" of requests that plagues many server-side architectures.

Next time, I'd probably look closer at whether I can push more of this into standard Next.js fetch caching, which handles deduplication automatically. However, for internal database calls or third-party SDKs that don't use fetch, the combination of cache and AsyncLocalStorage remains the gold standard for performance.

Frequently Asked Questions

Does React cache persist across different user requests? No. It’s scoped to the request lifecycle. Once the server finishes rendering the page and sends the response, the cache is cleared.

Can I use AsyncLocalStorage in Client Components? No. AsyncLocalStorage is a Node.js-only API. It will not work in the browser, and trying to import it in a Client Component will cause a build error.

Is React cache better than standard memoization? Yes, because it’s aware of the React render lifecycle. It handles the cache invalidation and promise resolution automatically, which is much safer than writing your own manual memoization wrappers.

Back to Blog

Similar Posts

ReactNext.jsJune 23, 20264 min read

Next.js Request Affinity: Optimizing Server-Side Data Locality

Next.js request affinity is vital for low-latency, stateful data access. Learn how to implement sticky routing to keep your server components near your data.

Read more
Next.jsReact
June 22, 2026
4 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.

Read more
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