Next.js 15 Circular Structure Error When passing `domNode` to another server component
Opened this issue · 17 comments
I'm not sure if this is a bug, or some problem with my NextJS environment, but in Next 14, I could pass domNode from within the replace() function to other server components, and keep my react customizations more organized. But after upgrading to Next 15, I'm getting the following error:
[ Server ] Error: Converting circular structure to JSON
--> starting at object with constructor 'Element'
| property 'prev' -> object with constructor 'Element'
--- property 'next' closes the circle
Is this a bug?
const options: HTMLReactParserOptions = {
replace(domNode) {
if (domNode instanceof Element && domNode.attribs) {
if (domNode.name === 'ul') {
return <CustomList node={domNode} />;
}
}
}
}Can you provide a reproducible example @webplantmedia? Is this happening on the server-side or client-side? Just curious: if you render with 'use client', does the same error appear?
Happening all on the server side. I read in HTML from an API. I parse the content like so:
const html = parse(content, options);My options are defined similar to the code here:
const options: HTMLReactParserOptions = {
replace(domNode) {
if (domNode instanceof Element && domNode.attribs) {
if (domNode.name === 'ul') {
return <CustomList node={domNode} />;
}
}
}
}Next 14, no error. Next 15, Circular Structure Error.
Gotcha I'll try to reproduce this in the example Next.js app in this repository when I have some time today
I upgraded the example Next.js app to v15 and I wasn't able to reproduce your error: https://github.com/remarkablemark/html-react-parser/tree/master/examples/nextjs
Let me know if there's anything I can adjust to reproduce your error
My suspicion is that it's coming from this line:
<CustomList node={domNode} />Do you call JSON.stringify on domNode in <CustomList>?
Even this is producing the error for me:
const options: HTMLReactParserOptions = {
replace(domNode) {
function test(node: any) {
console.log(node);
}
test(domNode);
},
};
const html = parse(content, options);
return <>{html}</>;Error output:
Error: Converting circular structure to JSON
--> starting at object with constructor 'Element'
| property 'next' -> object with constructor 'Element'
--- property 'prev' closes the circle
at test (rsc://React/Server/webpack-internal:///(rsc)/./components/blocks/parse-blocks.tsx?2:17:25)
at Object.replace (rsc://React/Server/webpack-internal:///(rsc)/./components/blocks/parse-blocks.tsx?3:19:13)
at ParseBlocks (rsc://React/Server/webpack-internal:///(rsc)/./components/blocks/parse-blocks.tsx?4:22:79)
at resolveErrorDev (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:1792:63)
at processFullStringRow (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2071:17)
at processFullBinaryRow (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2059:7)
at progress (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2262:17)

Do you get the same error if you do this in the example Next.js app? https://github.com/remarkablemark/html-react-parser/tree/master/examples/nextjs
Yes, I do get the same error when in the app directory. No error if in the pages directory. Here is my code in the app directory.
layout.tsx
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}page.tsx
import parse, { Element } from 'html-react-parser';
type Props = {
params: { slug: string };
};
export default async function Page({ params }: Props) {
return (
<main>
<h1 className="title">
{parse(
`
Welcome to <a href="https://nextjs.org">Next.js</a>
and HTMLReactParser!
`,
{
replace(domNode) {
function test(node: any) {
console.log(node);
}
test(domNode);
if (domNode instanceof Element && domNode.name === 'a') {
return (
<a href="https://nextjs.org" rel="noopener noreferrer">
Next.js
</a>
);
}
},
}
)}
</h1>
</main>
);
}Error:
Error: Converting circular structure to JSON
--> starting at object with constructor 'Text'
| property 'next' -> object with constructor 'Element'
--- property 'prev' closes the circle
at test (rsc://React/Server/webpack-internal:///(rsc)/./app/page.tsx?0:20:33)
at Object.replace (rsc://React/Server/webpack-internal:///(rsc)/./app/page.tsx?1:22:21)
at Page (rsc://React/Server/webpack-internal:///(rsc)/./app/page.tsx?2:14:84)
at resolveErrorDev (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:1792:63)
at processFullStringRow (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2071:17)
at processFullBinaryRow (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2059:7)
at progress (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2262:17)
package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"html-react-parser": "^5.1.18",
"next": "^15.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/node": "22.8.6",
"@types/react": "18.3.12",
"typescript": "5.6.3"
}
}Can you fork and create a branch with these changes? @webplantmedia
https://github.com/webplantmedia/html-react-parser
I just pushed a commit with the code. Thanks so much for looking into it! I have based a very big next js project in using this react parser. I'm hoping there is an easy fix to this without having to refactor lots of code.
https://github.com/webplantmedia/html-react-parser/tree/master/examples/nextjs
Thanks! I'll take a look later today @webplantmedia
Any insight on the nature of this error? Is it some new code procedure introduced in next js 15? If it is permanent, would I need to drop support of this parser if I need to stick with Next JS? Thank you again. I've been stuck on this for two days.
@webplantmedia this is happening because Next.js tries to render the data as JSON in a <script> tag:
There are 3 solutions:
- Add
'use client'to the top of the file<CustomList>but you will lose server-side rendering - Instead of passing
domNodeto the function, pass the specific keys and values - Remove circular references from
domNode: https://stackoverflow.com/questions/66091604/how-do-i-remove-all-circular-references-from-an-object-in-javascript
Sigh. I really needed to be able to loop inside other components. And keep it as a server component. Do you see this being a permanent move by nextjs 15 on how they handle complex objects?
I really appreciate your insight. Thanks so much.
@webplantmedia I'm not sure, but I think it's worth creating an issue or discussion on Next.js to confirm. For now, I think 3 could resolve the issue for you, but there may be a chance that it breaks your project.
I did!
I'm assuming if I remove circular reference, it would break domToReact(node?.children as DOMNode[], options) in other components. Let me know if that's not the case. Also, seems like a lot of extra recursion too for large html content.
But I can't even console log domNode if I want to debug in a server component.
Two other html parsers I looked into also have circular referencing.
Sigh.
@webplantmedia yes there's a possibility that if you remove circular reference that it would break domToReact(). This library is built using html-dom-parser, which uses htmlparser2 under the hood. That's where the circular references are coming from.
