nextjs - ✅(Solved) Fix `@next/third-parties` global `Window` augmentation breaks type inference due to index signature interaction with TypeScript mapped types [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#85555Fetched 2026-04-08 02:15:04
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
5
Author
Participants
Timeline (top)
commented ×1cross-referenced ×1issue_type_added ×1labeled ×1

Fix Action

Fixed

PR fix notes

PR #85556: fix(third-parties): remove global Window augmentation to avoid type degradation

Description (problem / solution / changelog)

Fixes #85555

Summary

Removes the global Window augmentation in @next/third-parties that introduced a string index signature ([key: string]: any]), which caused mapped types involving Window to collapse into { [k: string]: any }. The change uses a local cast instead, preserving runtime behavior while maintaining type safety across the ecosystem.

Details

  • Deleted packages/third-parties/src/types/google.ts global Window declaration.
  • Updated google/ga.tsx and google/gtm.tsx to locally cast window as { [key: string]: Object[] } before accessing dynamic data layer keys.

No runtime behavior changes.

Rationale

  • The dataLayer key name is dynamic; declaring it globally adds no type safety.
  • The global index signature caused far-reaching type degradation in apps, DOM utilities, and libraries.
  • Removing it restores consistency with lib.dom.d.ts and avoids polluting global scope.

Notes

  • Jest-based type tests are out of scope; this fix only targets type definitions.
  • While this issue surfaced clearly with @types/jsdom, it applies equally to any consumer using Omit<Window, ...> or similar type operations.
<!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # -->

Changed files

  • packages/third-parties/src/google/ga.tsx (modified, +2/-6)
  • packages/third-parties/src/google/gtm.tsx (modified, +1/-0)
  • packages/third-parties/src/types/google.ts (modified, +0/-7)

Code Example

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

---

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

---

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

---

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

---

const window = globalThis.window as unknown as { [key: string]: Object[] }
RAW_BUFFERClick to expand / collapse

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.

extent analysis

TL;DR

Remove the global Window augmentation from @next/third-parties to prevent type degradation of Window properties.

Guidance

  1. Identify the problematic augmentation: The global Window augmentation in @next/third-parties is causing the issue by introducing a string index signature ([key: string]: any) that makes keyof Window effectively string.
  2. Remove the global augmentation: Remove the global Window augmentation entirely from @next/third-parties to prevent type degradation of Window properties.
  3. Use local casts instead: Replace global augmentation with local casts at usage sites, such as const window = globalThis.window as unknown as { [key: string]: Object[] }, to retain runtime behavior without polluting global types.
  4. Verify the fix: After removing the global augmentation, verify that Window properties like document, location, and navigator are no longer typed as any and that downstream libraries and DOM shims work as expected.

Example

// Before (global augmentation)
declare global {
  interface Window {
    dataLayer?: Object[]
    [key: string]: any
  }
}

// After (local cast)
const window = globalThis.window as unknown as { [key: string]: Object[] }

Notes

This fix assumes that the global augmentation is not necessary for the functionality of @next/third-parties. If the augmentation is required, alternative solutions may need to be explored.

Recommendation

Apply the workaround by removing the global Window augmentation from @next/third-parties and using local casts instead, as this approach prevents type degradation of Window properties and aligns with the goal of preserving type safety and IntelliSense.

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