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
Summary
useInlineSVGcallsMath.random()on every render, which makes the component impossible to statically prerender in Next.js 16 withcacheComponentsenabled.src/modules/useInlineSVG.ts:27useRef's argument is evaluated eagerly on every render, and??evaluates its right operand wheneveruniqueHashis undefined — sorandomString(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
cacheComponentsit patches the non-deterministic globals to detect values it cannot reproduce —node_modules/next/dist/server/node-environment-extensions/random.js: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
next build:Note this is invisible in
next dev— dev renders every affected page correctly.Observation
The hash is only read when
uniquifyIDsis set (getNodereturns early otherwise atutils.ts:59, andupdateSVGAttributesatutils.ts:115), and only oncecontentexists — which requires the DOM, since loading happens in the mount effect behindcanUseDOM(). 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 (
useIdwould need React 18+).Workaround for anyone hitting this now
Pass a stable
uniqueHashat every call site:Happy to open a PR — I have the fix and a regression test ready. Filing this first per CONTRIBUTING.md.
Environment
react-inlinesvg4.5.0 (also 4.2.0, 4.4.1, 3.0.3)