Frontend randomly running into infinite loop when JSON-LD is on page (and cacheComponents is enabled)
Hi,
I'm fighting a super weird bug since 3 days. I upgraded Nextjs in my project from 15 to 16.2.2 and added cached components. Everything works really well, except one issue: When I refresh routes that have json-ld in them like explained in the docs (https://nextjs.org/docs/app/guides/json-ld), they randomly run into an infinite loop and ultimately crash the tab. It doesn't happen on every refresh tho, sometimes it already crashes when I open the url, sometimes I can refresh a few times before the crash happens.
I did a ton of digging to even find out that it is caused by the json-ld, but I can't find a solution except injecting it client side or removing it.
I also tried to replicate the issue in a minimal setup but couldn't really get it to the infinite loop. Since I don't really understand the Nextjs internals and I did a lot of debugging with Opus, here some things that I found out:
Env:
-Next.js 16.2.2, React 19.2.4, Turbopack (also using HeroUI v3)
- `cacheComponents: true` (PPR)
- App Router, async server components
- The error only appears on builds, not in dev. It only happens when cacheComponents is enabled.
**The symptom:**
On page load or after a handful of refreshes, the tab becomes completely unresponsive. CPU spikes to 130%. The HTML document is delivered successfully (verified with curl), but JS chunks stay "(pending)" in DevTools and the tab never recovers.
**Root cause:**
Pausing the frozen tab in DevTools lands inside React's PPR reveal function (`$RV`), specifically this loop:
```
for (; e.firstChild; ) f.insertBefore(e.firstChild, c);
```
The variables `e` and `f` are the same DOM element. Moving children from a node into itself never clears `firstChild` → infinite loop.
We also found that Next.js PPR generates duplicate HTML element IDs in the streaming output. For example, two separate `<div hidden id="S:4">` elements — one from the prerendered fallback, one from the dynamic content. When React's `$RC` function calls `document.getElementById("S:4")`, it can return the wrong element depending on timing, which leads to the `e === f` condition. <- This was a conclusion by Opus, but we later removed a lot of my Suspense boundaries which fixed the duplicate ID thing, but it still crashed.
It's basically way to far inside Nextjs for me to understand whats going on and I'm really out of ideas. I invested like 3 days to find the issue, try to build a minimal replication to be able open a Github issue (which didn't work) and I just want my json-ld in there that worked before.
json-ld code is basically the same like in the example in the docs:
```
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(data).replace(/</g, "\\u003c"),
}}
/>
);
```