vercel / vercel/next.js

`@next/third-parties` global `Window` augmentation breaks type inference due to index signature interaction with TypeScript mapped types

Open
#85,555 1 comment 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

TypeScript
Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/xenodochial-scooby-dj58gg?workspaceId=ws_DTjhos2wnm6iYsDknYvGYP

To Reproduce
  1. Run nvm install lts/jod to use a recent Node LTS version (required for ESM imports).
  2. Run npm run types or open the editor and check the TypeScript diagnostics.
  3. Observe that in page.tsx, dom.window.document and related DOM properties are typed as any instead of Document.
Current vs. Expected behavior

Current:

When @next/third-parties is installed, it globally augments Window with a string index signature ([key: string]: any]).
This causes Omit<Window, ...> and similar mapped types to collapse into { [k: string]: any }.
As a result, downstream libraries and DOM shims (like JSDOM) lose all property type information—document, location, navigator, etc., all become any.

Expected:

The Window type should behave as defined in lib.dom.d.ts: omitting or extending it should preserve concrete properties and not degrade to { [k: string]: any }.
Removing the global index signature from @next/third-parties resolves this and restores proper typing.

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: 22.21.1
  npm: 10.9.4
  Yarn: 1.22.19
  pnpm: 8.10.2
Relevant Packages:
  next: 16.0.2-canary.1 // Latest available version is detected (16.0.2-canary.1).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.3.3
Next.js Config:
  output: N/A
Which area(s) are affected? (Select all that apply)

TypeScript

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

next build (local)

Additional context
What happened

@next/third-parties augments Window globally as follows:

declare global {
  interface Window {
    dataLayer?: Object[]
    [key: string]: any
  }
}

Adding a string index signature ([key: string]: any) makes keyof Window effectively string.
As a result, TypeScript mapped types such as Pick or Omit collapse:

type Example = Omit<Window, "document">
// becomes roughly { [k: string]: any }

All concrete properties of Window are erased, and Example degenerates into a generic indexable object.
This is by design in TypeScript, but causes real issues in downstream packages and apps that rely on Window-based types.

Impact

This global augmentation affects any code that performs type transformations on Window, for example:

  • Libraries or tests using DOM shims (e.g., JSDOM)
  • Server-side rendering scenarios (SSR/SSG/ISR) that rely on DOM-oriented utilities such as DOMPurify
  • Any user code importing or re-declaring Omit<Window, ...> for isolation, mock, or type refinement

In these cases, properties like document, location, or navigator become any, breaking type safety and IntelliSense.

For instance, @types/jsdom defines a DOMWindow type as:

interface DOMWindow extends Omit<Window, "top" | "self" | "window"> {
    ...
}

When the global index signature exists, this type collapses to { [k: string]: any }.

Why this occurs

This is not a TypeScript bug.
When a string index signature is present, the compiler treats keyof as string, so mapped types (Pick, Omit, etc.) cannot preserve known keys.

Why this matters for @next/third-parties

In the Google integrations (@next/third-parties/google), the dataLayer key name is dynamic — it defaults to "dataLayer" but can be overridden by the user.
Therefore, declaring a fixed dataLayer property on the global Window type provides limited benefit, while the global string index signature introduces a broad ecosystem regression.

Proposed fix
  • Remove the global Window augmentation entirely from @next/third-parties.
  • Replace global augmentation with a local cast at usage sites:
    const window = globalThis.window as unknown as { [key: string]: Object[] }
    
    This retains runtime behavior without polluting global types.

This aligns with the goal of PR #68219 while addressing an additional class of breakages observed in the ecosystem.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the global Window augmentation in @next/third-parties and the Google integration code that uses dataLayer. Reproduce the issue with the linked CodeSandbox using npm run types, then verify that removing the broad index signature preserves document, location, and navigator types in Window-based mapped types.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, typescript
Domain
developer-experience, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.