Skip to content

Math.random() on every render breaks static prerendering (Next.js cacheComponents) #253

Description

@pedrosousa13

Summary

useInlineSVG calls Math.random() on every render, which makes the component impossible to statically prerender in Next.js 16 with cacheComponents enabled.

src/modules/useInlineSVG.ts:27

const hash = useRef(uniqueHash ?? randomString(8));

useRef's argument is evaluated eagerly on every render, and ?? evaluates its right operand whenever uniqueHash is undefined — so randomString(8) runs each time even though the ref only keeps the first result. v3 did the same thing in the class constructor; 4.x moved it into the hook.

Why it breaks prerendering

Next.js 16 prerenders client components at build time and stores the HTML. Under cacheComponents it patches the non-deterministic globals to detect values it cannot reproduce — node_modules/next/dist/server/node-environment-extensions/random.js:

We extend Math.random() during builds and revalidates to ensure that prerenders don't observe randomness

A patched call aborts the enclosing Suspense boundary. Next then takes its "postponed but nothing dynamic was used" path and freezes every boundary from that point on as client-rendered in the stored artifact. There is no request-time recovery.

The failure is silent — the abort is deliberately transparent to the caller — so a well-structured app just serves a near-empty document. On our site every prerendered page dropped to a 487-byte shell, and the header logo was the first thing to trip it.

Reproduction

// app/probe/Probe.tsx
'use client';
import SVG from 'react-inlinesvg';

export const Probe = () => <SVG src="https://example.com/logo.svg" />;
// next.config.mjs
export default { cacheComponents: true };

next build:

Route "/probe": Next.js encountered the unstable value `Math.random()` in a Client Component.
Error occurred prerendering page "/probe"

Note this is invisible in next dev — dev renders every affected page correctly.

Observation

The hash is only read when uniquifyIDs is set (getNode returns early otherwise at utils.ts:59, and updateSVGAttributes at utils.ts:115), and only once content exists — which requires the DOM, since loading happens in the mount effect behind canUseDOM(). So the hash can never be needed during a server render, and generating it there is pure waste.

Generating it on first read instead of on every render fixes this without changing any behaviour or dropping React 16.8/17 support (useId would need React 18+).

Workaround for anyone hitting this now

Pass a stable uniqueHash at every call site:

<SVG src={src} uniqueHash={useId()} />

Happy to open a PR — I have the fix and a regression test ready. Filing this first per CONTRIBUTING.md.

Environment

  • react-inlinesvg 4.5.0 (also 4.2.0, 4.4.1, 3.0.3)
  • Next.js 16.3.1, React 19.2.3

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions