gilbarbara / gilbarbara/react-inlinesvg
Math.random() on every render breaks static prerendering (Next.js cacheComponents)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.3k
- Forks
- 105
- PR merge metrics
- No merged PRs in 30d
Description
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-inlinesvg4.5.0 (also 4.2.0, 4.4.1, 3.0.3)- Next.js 16.3.1, React 19.2.3
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/modules/useInlineSVG.ts:27, then inspect utils.ts at lines 59 and 115 to confirm when the hash is read. Reproduce the failure with the provided Next.js cacheComponents example and run the available regression tests. Done means Math.random() is not evaluated during renders, static prerendering succeeds, and existing behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, react, typescript
- Domain
- build-system, frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 87/100