This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
frontendcritical PriorityID: nextjs-15-async-request-cache-isolation

Next.js 15 Async Request Scope & Multi-Tenant Cache Isolation Guardrail

Enforces strict boundary isolation between Next.js 15 asynchronous dynamic request APIs (await cookies(), await headers(), searchParams) and shared data caching mechanisms (unstable_cache, Data Cache, shared CDN edge layers). Prevents critical cross-tenant data poisoning and authorization bypasses caused by persisting tenant-scoped or user-scoped data into shared cache partitions.

Prohibited Anti-Pattern: Persisting Tenant-Specific Data into Shared Data Caches via Dynamic Async Request Closures

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Next.js 15 Async Request Scope & Multi-Tenant Cache Isolation Guardrail - Enforces strict boundary isolation between Next.js 15 asynchronous dynamic request APIs (await cookies(), await headers(), searchParams) and shared data caching mechanisms (unstable_cache, Data Cache, shared CDN edge layers). Prevents critical cross-tenant data poisoning and authorization bypasses caused by persisting tenant-scoped or user-scoped data into shared cache partitions.
globs: app/**/*.{ts,tsx}, src/app/**/*.{ts,tsx}, lib/**/*.{ts,tsx}, src/lib/**/*.{ts,tsx}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/nextjs-15-async-request-cache-isolation"
---

# Next.js 15 Async Request Scope & Multi-Tenant Cache Isolation Guardrail

> **Severity**: CRITICAL | **Category**: frontend
> **Anti-Pattern Warning**: Persisting Tenant-Specific Data into Shared Data Caches via Dynamic Async Request Closures

## System Constraints
You are a Principal Software Architect enforcing Next.js 15 Asynchronous Request Scope Cache Isolation Guardrails. In Next.js 15, dynamic request APIs (cookies(), headers(), params, searchParams) are asynchronous Promises, and fetch requests default to no-store. You must rigorously prevent cross-tenant cache contamination, ensuring that any dynamic request context is NEVER closed over or persisted into shared caching layers (unstable_cache, force-cache, or global revalidation tags) without strict cryptographic tenant partitioning or dynamic scope isolation.

## Strict Guidelines
- Engineers MUST ALWAYS treat Next.js 15 request scope APIs (`cookies()`, `headers()`, `params`, `searchParams`) as asynchronous Dynamic Data Sources that opt out of static optimization.
- Engineers MUST NOT invoke or close over dynamic request helpers (`await cookies()`, `await headers()`) inside the callback of `unstable_cache`, as this violates isolation boundaries and risks global data leakage across sessions.
- Engineers MUST explicitly incorporate verified tenant IDs and security contexts into `keyParts` whenever `unstable_cache` is utilized in multi-tenant environments, or default strictly to React `cache()` for per-request memoization.
- Code SHOULD prefer `connection()` from `next/server` before invoking dynamic APIs when explicitly opting into dynamic request handling to guarantee clean separation from static build-time paths.
- Services MUST NEVER configure `fetch` with `cache: 'force-cache'` or persistent Next.js Data Cache tags when sending Authorization headers, tenant-specific cookies, or session tokens.
- Components NEVER store user-personalized UI trees in shared CDN/Edge caches; layouts and components reading session state MUST render in dynamic request scopes.

## Bad vs Good Implementations
### Cross-Tenant Cache Poisoning in unstable_cache vs Isolated Multi-Tenant Scoping

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Next.js evaluates unstable_cache globally across requests and tenants. Omitting tenantId from the cache keyParts and attempting to read request headers inside the callback poisons the global cache with the first tenant's data, serving it to subsequent callers regardless of identity.
```typescript
import { unstable_cache } from 'next/cache';
import { headers } from 'next/headers';
import { db } from '@/lib/db';

// CRITICAL: headers() closed inside unstable_cache or missing tenant key
export async function getTenantInvoices() {
  return unstable_cache(
    async () => {
      // Poisoning hazard: reading async headers inside cached callback
      // or omitting tenantId from keyParts permits Tenant B to read Tenant A's cached invoices
      const headerStore = await headers();
      const tenantId = headerStore.get('x-tenant-id');
      return db.invoices.findMany({ where: { tenantId } });
    },
    ['tenant-invoices'], // Shared global cache key across ALL tenants!
    { revalidate: 3600, tags: ['invoices'] }
  )();
}
```

#### Production Standard (Correct)
> **Standard**: Separates dynamic request extraction (using async headers() and React request-level cache()) from the shared caching primitive (unstable_cache). Guarantees that unstable_cache receives tenantId strictly as an external cache keyPart and isolated revalidation tag, completely eliminating cross-tenant leakage.
```typescript
import { unstable_cache } from 'next/cache';
import { headers } from 'next/headers';
import { connection } from 'next/server';
import { cache } from 'react';
import { db } from '@/lib/db';

// 1. React cache() scopes memoization strictly to the current request lifecycle
export const getRequestTenantId = cache(async (): Promise<string> => {
  await connection(); // Explicitly marks boundary as dynamic request
  const headerList = await headers();
  const tenantId = headerList.get('x-tenant-id');
  if (!tenantId || !/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
    throw new Error('Invalid or missing tenant identifier');
  }
  return tenantId;
});

// 2. Pure data fetcher with explicit tenant key isolation
const fetchCachedTenantInvoices = (tenantId: string) =>
  unstable_cache(
    async () => db.invoices.findMany({ where: { tenantId } }),
    ['tenant-invoices', tenantId], // Strictly partitioned by verified tenantId
    { revalidate: 3600, tags: [`tenant:${tenantId}:invoices`] }
  )();

export async function getTenantInvoices() {
  const tenantId = await getRequestTenantId();
  return fetchCachedTenantInvoices(tenantId);
}
```

### Authenticated Fetch Cache Poisoning vs Request-Bound Dynamic Fetch

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Using 'force-cache' on authenticated fetches causes the Next.js Data Cache to persist the response under a global fetch hash. Another user requesting their profile may receive the poisoned cached response of the first user.
```typescript
import { cookies } from 'next/headers';

export async function getUserProfile() {
  const cookieStore = await cookies();
  const token = cookieStore.get('session_token')?.value;

  // VULNERABILITY: Combining authenticated bearer token with force-cache
  const res = await fetch('https://api.internal/user/profile', {
    headers: { Authorization: `Bearer ${token}` },
    cache: 'force-cache', // Persists user profile into the shared Next.js Data Cache
    next: { tags: ['user-profile'] },
  });

  return res.json();
}
```

#### Production Standard (Correct)
> **Standard**: Explicitly enforces 'no-store' for authenticated requests, ensuring personal user profiles are evaluated strictly per-request and never stored in the shared multi-tenant Next.js Data Cache or edge caches.
```typescript
import { cookies } from 'next/headers';
import { connection } from 'next/server';

export async function getUserProfile() {
  await connection(); // Signals runtime dynamic execution context
  const cookieStore = await cookies();
  const token = cookieStore.get('session_token')?.value;

  if (!token) {
    throw new Error('Unauthorized');
  }

  // Authenticated dynamic fetches MUST NOT use persistent shared cache
  const res = await fetch('https://api.internal/user/profile', {
    headers: { Authorization: `Bearer ${token}` },
    cache: 'no-store', // Next.js 15 default; explicitly enforced for auth payloads
  });

  if (!res.ok) {
    throw new Error(`Profile fetch failed: ${res.status}`);
  }

  return res.json();
}
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Next.js 15 Async Request Scope & Multi-Tenant Cache Isolation Guardrail

Next.js 15 Async Request Scope & Multi-Tenant Cache Isolation Guardrail defines an industrial-grade behavioral standard for frontend engineering. Enforces strict boundary isolation between Next.js 15 asynchronous dynamic request APIs (await cookies(), await headers(), searchParams) and shared data caching mechanisms (unstable_cache, Data Cache, shared CDN edge layers). Prevents critical cross-tenant data poisoning and authorization bypasses caused by persisting tenant-scoped or user-scoped data into shared cache partitions. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Persisting Tenant-Specific Data into Shared Data Caches via Dynamic Async Request Closures Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftPersisting Tenant-Specific Data into Shared Data Caches via Dynamic Async Request ClosuresCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS treat Next.js 15 request scope APIs (`cookies()`, `headers()`, `params`, `searchParams`) as asynchronous Dynamic Data Sources that opt out of static optimization.

Engineers MUST ALWAYS treat Next.js 15 request scope APIs (`cookies()`, `headers()`, `params`, `searchParams`) as asynchronous Dynamic Data Sources that opt out of static optimization.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT invoke or close over dynamic request helpers (`await cookies()`, `await headers()`) inside the callback of `unstable_cache`, as this violates isolation boundaries and risks global data leakage across sessions.

Engineers MUST NOT invoke or close over dynamic request helpers (`await cookies()`, `await headers()`) inside the callback of `unstable_cache`, as this violates isolation boundaries and risks global data leakage across sessions.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Engineers MUST explicitly incorporate verified tenant IDs and security contexts into `keyParts` whenever `unstable_cache` is utilized in multi-tenant environments, or default strictly to React `cache()` for per-request memoization.

Engineers MUST explicitly incorporate verified tenant IDs and security contexts into `keyParts` whenever `unstable_cache` is utilized in multi-tenant environments, or default strictly to React `cache()` for per-request memoization.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Code SHOULD prefer `connection()` from `next/server` before invoking dynamic APIs when explicitly opting into dynamic request handling to guarantee clean separation from static build-time paths.

Code SHOULD prefer `connection()` from `next/server` before invoking dynamic APIs when explicitly opting into dynamic request handling to guarantee clean separation from static build-time paths.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Services MUST NEVER configure `fetch` with `cache

Services MUST NEVER configure `fetch` with `cache: 'force-cache'` or persistent Next.js Data Cache tags when sending Authorization headers, tenant-specific cookies, or session tokens.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
6

Components NEVER store user-personalized UI trees in shared CDN/Edge caches

Components NEVER store user-personalized UI trees in shared CDN/Edge caches; layouts and components reading session state MUST render in dynamic request scopes.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Next.js 15 Async Request Scope & Multi-Tenant Cache Isolation Guardrail critical for AI coding assistants?

Enforces strict boundary isolation between Next.js 15 asynchronous dynamic request APIs (await cookies(), await headers(), searchParams) and shared data caching mechanisms (unstable_cache, Data Cache, shared CDN edge layers). Prevents critical cross-tenant data poisoning and authorization bypasses caused by persisting tenant-scoped or user-scoped data into shared cache partitions. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Persisting Tenant-Specific Data into Shared Data Caches via Dynamic Async Request Closures) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related FRONTEND Rules

View all rules →
frontend
119

SolidStart 1.0 Server Functions & Async Boundaries Production Standard

SolidStart 1.0 relies on the Vinxi bundler to split `server$` closures into independent HTTP POST RPC endpoints. Direct lexical closure over client signals within `server$` scopes or failure to demarcate async boundaries with `<Suspense>` causes silent serialization failures, client-bundle secret leaks, and SSR hydration mismatches. Enforcing strict parameter passing, runtime input validation, and Solid async primitives maintains security and fine-grained reactivity across network boundaries.

frontend
119

Astro 5 Actions & Content Layer Production Standard

Enforces strict type safety and architectural correctness for Astro 5 Server Actions and the zero-waterfall Content Layer API. Prevents runtime contract mismatches, hydration mismatches, and unhandled mutation exceptions across dynamic and prerendered boundaries. Failure to follow these patterns leads to silent data fetching waterfalls, build-time cache corruptions, and unhandled server action exceptions.

frontend
51

Nuxt 4 Nitro Server Engine & Shared Directory Production Standard

Nuxt 4 enforces strict boundary segregation between client UI in the app/ directory, backend endpoints in server/, and universal contracts in shared/. Violating cross-boundary isolation or maintaining module-scoped state in Nitro server handlers causes catastrophic cross-request data corruption and hydration mismatches under concurrent production traffic. Architects must enforce request-isolated H3 event contexts, universal serialization schemas, and strict path-alias boundaries.

1 Rule in Stack

Next.js