nextjs - ✅(Solved) Fix RequestInit shouldnt be extended with 'next' key ? [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86898Fetched 2026-04-08 02:08:28
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
1
Author
Timeline (top)
subscribed ×2commented ×1cross-referenced ×1issue_type_added ×1

Root Cause

Looking at the cache file in .next/cache/fetch-cache shows that the cache was created but with the tags left empty ("tags":[]). This happens because we pass a Request instance to the fetch API. The Request instance does not preserve the next property after construction.

Fix Action

Fix / Workaround

Looking at patch-fetch.ts:337-351, Next.js extracts tags like this:

const getNextField = (field: 'revalidate' | 'tags') => {
  return typeof init?.next?.[field] !== 'undefined'
    ? init?.next?.[field]           // Checks second parameter first
    : isRequestInput
      ? (input as any).next?.[field]  // Then checks Request object
      : undefined
}

PR fix notes

PR #87072: fix: patch fetch() signature instead of extending RequestInit

Description (problem / solution / changelog)

Fixing a bug

What?

This PR changes how the next option (with revalidate and tags) is typed for fetch requests. Instead of extending the global RequestInit interface, we now only patch the fetch() function signature to accept NextFetchRequestInit.

Why?

Previously, extending the global RequestInit interface created a type mismatch with runtime behavior. When using new Request({ next: {...} }), JavaScript's native Request constructor drops any properties that aren't part of the standard RequestInit interface, so the next option would be silently ignored. This created confusion because TypeScript would allow it, but it wouldn't actually work at runtime.

By restricting the next option to only be available through fetch() calls, we:

  • Align TypeScript types with actual runtime behavior
  • Prevent users from trying to use next with new Request() where it won't work
  • Make the API more explicit about where the next option is supported

How?

  1. Removed global RequestInit extension in packages/next/types/global.d.ts

    • Removed the interface extension that added next?: NextFetchRequestConfig to all RequestInit types
  2. Created NextFetchRequestInit type and augmented only fetch()

    • Added NextFetchRequestInit interface extending RequestInit with the next property
    • Used declare global to augment only the fetch() function signature to accept NextFetchRequestInit
    • Added export {} to make the file a module (required for global augmentations)
  3. Updated patched fetch implementation in packages/next/src/server/lib/patch-fetch.ts

    • Updated function signature to use NextFetchRequestInit instead of RequestInit
    • Added local type definitions for consistency

Now next can only be passed through fetch(), not through new Request(), which matches the actual runtime behavior. Fixes #86898

Changed files

  • packages/next/src/server/lib/patch-fetch.ts (modified, +26/-12)
  • packages/next/types/global.d.ts (modified, +11/-1)

Code Example

2. Fetch from the new api we created. 
2. Invalidate that tag, say using another api call or something `invalidateTag('test-todo')`
3. Refetch from the initial api. 

### Current vs. Expected behavior

We would expect the fetch to bypass the cache and hit the remote endpoint again, since we invalidated the cache. However, this does not invalidate the cache as expected.

Looking at the cache file in `.next/cache/fetch-cache` shows that the cache was created but with the tags left empty (`"tags":[]`). This happens because we pass a Request instance to the fetch API. The Request instance does not preserve the `next` property after construction.

Looking at [patch-fetch.ts:337-351](packages/next/src/server/lib/patch-fetch.ts#L337), Next.js extracts tags like this:

---

The code attempts to read `next.tags` from the `Request` object, but this fails because the native Request constructor doesn't preserve custom properties like `next`.

Root Cause

This issue occurs because Next.js extends the RequestInit interface:

---

This interface is used in both:

1. The second parameter of the fetch() API  (works correctly)
2. The constructor for Request  (doesn't work - custom properties are dropped)

The Problem: While TypeScript allows passing next to the Request constructor (because of the interface extension), the native Request constructor silently drops this custom property at runtime. This creates a mismatch between what TypeScript permits and what actually works.

Suggested Fix: The next property should only extend the second parameter of the fetch() API, not the RequestInit interface used by the Request constructor. This would prevent the misleading TypeScript types that suggest this pattern works when it doesn't.

### Provide environment information
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/lingering-cookies-4vw8hq?workspaceId=ws_LGFtWwovwmKaYzyN8ocJvZ

To Reproduce

  1. Create a new route Handler that cached api that uses tags like this
import { NextResponse } from 'next/server';

export async function GET() {
  // Create a new Request object with Next.js caching options
  const request = new Request('https://jsonplaceholder.typicode.com/todos/1', {
    next: {
      tags: ['test-todo']
    }
  });

  // Pass the Request object to fetch
  const response = await fetch(request);
  const data = await response.json();

  return NextResponse.json(data);
}
  1. Fetch from the new api we created.
  2. Invalidate that tag, say using another api call or something invalidateTag('test-todo')
  3. Refetch from the initial api.

Current vs. Expected behavior

We would expect the fetch to bypass the cache and hit the remote endpoint again, since we invalidated the cache. However, this does not invalidate the cache as expected.

Looking at the cache file in .next/cache/fetch-cache shows that the cache was created but with the tags left empty ("tags":[]). This happens because we pass a Request instance to the fetch API. The Request instance does not preserve the next property after construction.

Looking at patch-fetch.ts:337-351, Next.js extracts tags like this:

const getNextField = (field: 'revalidate' | 'tags') => {
  return typeof init?.next?.[field] !== 'undefined'
    ? init?.next?.[field]           // Checks second parameter first
    : isRequestInput
      ? (input as any).next?.[field]  // Then checks Request object
      : undefined
}

The code attempts to read next.tags from the Request object, but this fails because the native Request constructor doesn't preserve custom properties like next.

Root Cause

This issue occurs because Next.js extends the RequestInit interface:

interface RequestInit {
  next?: NextFetchRequestConfig | undefined
}

This interface is used in both:

  1. The second parameter of the fetch() API ✅ (works correctly)
  2. The constructor for Request ❌ (doesn't work - custom properties are dropped)

The Problem: While TypeScript allows passing next to the Request constructor (because of the interface extension), the native Request constructor silently drops this custom property at runtime. This creates a mismatch between what TypeScript permits and what actually works.

Suggested Fix: The next property should only extend the second parameter of the fetch() API, not the RequestInit interface used by the Request constructor. This would prevent the misleading TypeScript types that suggest this pattern works when it doesn't.

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Sun Aug  6 20:05:33 UTC 2023
  Available memory (MB): 4102
  Available CPU cores: 2
Binaries:
  Node: 20.12.1
  npm: 10.5.0
  Yarn: 1.22.19
  pnpm: 8.15.6
Relevant Packages:
  next: 16.1.0-canary.15 // Latest available version is detected (16.1.0-canary.15).
  eslint-config-next: N/A
  react: 19.2.1
  react-dom: 19.2.1
  typescript: 5.9.3
Next.js Config:
  output: N/A



from codesandbox ^

Which area(s) are affected? (Select all that apply)

Route Handlers

Which stage(s) are affected? (Select all that apply)

next dev (local), Vercel (Deployed), Other (Deployed), next start (local)

Additional context

No response

extent analysis

TL;DR

Pass the next property directly to the fetch API instead of creating a Request object to ensure cache tags are properly set.

Guidance

  • When creating a new Request object, the next property is silently dropped by the native Request constructor, causing cache tags to be empty.
  • To fix this, pass the next property directly to the fetch API, like this: fetch('https://jsonplaceholder.typicode.com/todos/1', { next: { tags: ['test-todo'] } }).
  • Verify that the cache is properly invalidated by checking the cache file in .next/cache/fetch-cache after refetching from the initial API.
  • Consider updating the NextFetchRequestConfig interface to only extend the second parameter of the fetch API, not the RequestInit interface used by the Request constructor.

Example

import { NextResponse } from 'next/server';

export async function GET() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', {
    next: {
      tags: ['test-todo']
    }
  });
  const data = await response.json();

  return NextResponse.json(data);
}

Notes

This fix assumes that the next property is only used for cache tags and does not affect other functionality. If the next property is used for other purposes, additional modifications may be necessary.

Recommendation

Apply the workaround by passing the next property directly to the fetch API, as this ensures that cache tags are properly set and allows for correct cache invalidation.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING