Skip to content

RequestInit shouldnt be extended with 'next' key ? #86898

@noelzubin

Description

@noelzubin

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions