Skip to content

fix: guard render-time window/matchMedia access for SSR (DecayCard, PixelCard, Masonry) - #1006

Merged
DavidHDev merged 1 commit into
DavidHDev:mainfrom
zbysir:feat/fix-ssr-window-guards
Jul 23, 2026
Merged

fix: guard render-time window/matchMedia access for SSR (DecayCard, PixelCard, Masonry)#1006
DavidHDev merged 1 commit into
DavidHDev:mainfrom
zbysir:feat/fix-ssr-window-guards

Conversation

@zbysir

@zbysir zbysir commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

DecayCard, PixelCard and Masonry access browser globals during render (in useRef / useState initializers), so they throw under SSR (e.g. Next.js, or any renderToString environment) before the component ever mounts:

ReferenceError: window is not defined

Same class of bug fixed for TargetCursor in #911; I've reused the same typeof window guard style.

Changes

Guarded only the render-time accesses — effect/handler code is untouched.

  • DecayCard — the cursor and winsize useRef initializers read window.innerWidth/innerHeight. Guarded with typeof window !== 'undefined' ? … : 0. The existing resize/mousemove effect overwrites these on mount, so client behavior is unchanged.
  • PixelCardreducedMotion = useRef(window.matchMedia(...).matches) runs at render. Guarded with typeof window !== 'undefined' && … (→ false on the server, i.e. motion not reduced).
  • MasonryuseMedia's get() is passed to useState(get), so it runs once during render and calls matchMedia. Added an early if (typeof window === 'undefined') return defaultValue;.

All three were updated across all 4 variants (JS-CSS, JS-TW, TS-CSS, TS-TW) per the contributing guide.

Notes / testing

  • No API or client-behavior changes; server-render safety only.
  • Verified the changed files parse and are Prettier-clean (prettier --check).
  • Scope kept tight: only components with unguarded render-time access. Others referencing window do so inside effects/handlers or already guard with typeof window, so they're SSR-safe and left as-is.

DecayCard, PixelCard and Masonry read browser globals during render
(in useRef/useState initializers), which throws "window is not defined"
or "matchMedia is not defined" under SSR (e.g. Next.js). Guard each with
a typeof check, matching the pattern used in DavidHDev#911.

- DecayCard: guard window.innerWidth/innerHeight in the cursor/winsize
  useRef initializers (fallback 0)
- PixelCard: guard window.matchMedia in the reducedMotion useRef
  initializer (fallback false)
- Masonry: guard matchMedia in the useMedia `get` initializer consumed by
  useState (fallback defaultValue)

Effect/handler code is unchanged. Updated across all 4 variants of each
component per CONTRIBUTING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DavidHDev

Copy link
Copy Markdown
Owner

nice, thanks!

@DavidHDev
DavidHDev merged commit 67140d3 into DavidHDev:main Jul 23, 2026
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.

2 participants