Skip to content

fix(react): guard ThemeProvider default context against undefined document#2478

Merged
frankensteinke merged 2 commits into
developfrom
fix/theme-provider-ssr-document-guard
Jul 21, 2026
Merged

fix(react): guard ThemeProvider default context against undefined document#2478
frankensteinke merged 2 commits into
developfrom
fix/theme-provider-ssr-document-guard

Conversation

@frankensteinke

Copy link
Copy Markdown
Contributor

Summary

ThemeProvider's default parameter context = document?.body throws ReferenceError: document is not defined under any SSR runtime (e.g. Next.js App Router prerendering a "use client" component). Optional chaining only guards against null/undefined values — it does not protect against an undeclared global, so evaluating the document identifier itself throws before ?. ever runs.

This makes <ThemeProvider> (without an explicit context prop) unusable in any SSR framework. Passing context={null} works around it, confirming the default param is the cause.

Root cause / history

Introduced on 2024-01-31 in #1328 — a PR that intended to make ThemeProvider SSR-safe (document.bodydocument?.body) but used optional chaining, which never actually protected against the undeclared global. This is a pre-existing bug on develop, independent of module format (reproduces on CJS today).

Fix

-  context = document?.body,
+  context = typeof document !== 'undefined' ? document.body : undefined,

typeof is safe on undeclared globals. In the browser, behavior is identical (document.body); under SSR, context is undefined, and the component's existing context?. guards (effects + MutationObserver) already bail out on a falsy context.

Tests

  • New: theme.ssr.test.tsx runs under @jest-environment node, where document genuinely doesn't exist, and asserts renderToString(<ThemeProvider …>) doesn't throw. Verified red without the fix (exact ReferenceError at theme.tsx:32) → green with it. A separate node-env file is used rather than deleting global.document in the jsdom suite, which destabilizes jsdom's async teardown.
  • setupTests.ts: guarded the navigator.clipboard / document.execCommand shims with typeof … !== 'undefined' so the shared setup also runs under the node environment.

Verification

  • All context tests pass; the clipboard/execCommand-dependent suites (CopyButton, Code) plus TreeView still pass — the setup guard preserves jsdom behavior.
  • Lint + prettier clean on touched files.

…ument

The `context = document?.body` default in ThemeProvider threw
`ReferenceError: document is not defined` under SSR (e.g. Next.js App
Router prerendering a client component). Optional chaining only guards
null/undefined values, not an undeclared global, so reading `document`
itself throws first.

Use `typeof document !== 'undefined'` to fall back to `undefined` on the
server; the component's existing `context?.` guards already handle a
missing context. Browser behavior is unchanged.

Introduced in #1328, which intended to make ThemeProvider SSR-safe but
used optional chaining that never actually protected against the
undeclared global.

- Add a node-environment regression test (theme.ssr.test.tsx) that
  renders ThemeProvider via renderToString with no document present.
- Guard the DOM shims in setupTests.ts so shared setup runs under the
  node test environment too.
@frankensteinke
frankensteinke requested a review from a team as a code owner July 16, 2026 12:57
Copilot AI review requested due to automatic review settings July 16, 2026 12:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an SSR crash in the React ThemeProvider default context parameter by avoiding direct evaluation of the document global when it may be undeclared (e.g., Node/SSR runtimes), and adds regression coverage for that scenario.

Changes:

  • Updated ThemeProvider’s default context to use a typeof document !== 'undefined' guard to prevent ReferenceError under SSR.
  • Guarded DOM-dependent clipboard/execCommand shims in the shared Jest setup so it can run under a Node test environment.
  • Added a Node-environment SSR test ensuring renderToString(<ThemeProvider />) does not throw when document is undefined.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
packages/react/src/setupTests.ts Adds typeof guards so DOM shims don’t execute when navigator/document are absent (node env).
packages/react/src/contexts/theme.tsx Makes the context default SSR-safe by guarding access to document.body.
packages/react/src/contexts/theme.ssr.test.tsx Introduces a node-environment regression test covering the SSR crash scenario.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@aws-amplify-us-east-1

Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-2478.d15792l1n26ww3.amplifyapp.com

chornonoh-vova
chornonoh-vova previously approved these changes Jul 16, 2026
rheisler-deque
rheisler-deque previously approved these changes Jul 20, 2026

@rheisler-deque rheisler-deque left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR looks great, nice job testing to make sure the fix works and to protect against regression!

Comment thread packages/react/src/contexts/theme.ssr.test.tsx Outdated
@frankensteinke
frankensteinke merged commit ae70b3c into develop Jul 21, 2026
8 checks passed
@frankensteinke
frankensteinke deleted the fix/theme-provider-ssr-document-guard branch July 21, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants