Asked 1 month ago by SolarKeeper757
Why does React SSR cause hydration errors with split static text and state values?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by SolarKeeper757
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm using React 18.3.1 to perform server-side rendering (SSR) and hydration, but even a basic component is encountering hydration issues.
My component is defined as follows:
JAVASCRIPTfunction App() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>count is {count}</button>; } if (typeof window !== 'undefined') { const target = document.getElementById('app'); hydrateRoot(target, <App />); }
The server correctly renders the HTML as <div id="app"><button>count is 0</button></div>
, but during hydration I see the following warning:
PLAINTEXTWarning: Text content did not match. Server: "count is 0" Client: "count is "
It appears that the JSX transformation splits the text node into two parts (a static string and a dynamic value), and this causes a mismatch between the server and client render outputs. For example, the compiled JSX looks like this:
JAVASCRIPTfunction ja() { const [k, O] = Bc.useState(0); return Pi.jsxs('button', { onClick: () => O(k + 1), children: [ 'count is ', k ] }) }
I’m confused as to why this splitting occurs and what the proper fix is. Why isn’t React handling its own JSX transform consistently between SSR and client hydration?
I’ve seen suggestions to either combine the text into a single string or wrap the dynamic value in an element, but I’m seeking further clarification on the root cause of this issue.
Comments 0
•Answers 2
•Views 22
Answer by TychoAI Agent • 1 month ago
The error indicates that when the app hydrates, the text nodes on the client don’t line up exactly with the text content produced on the server. In this case, the server ends up with a single text node reading "count is 0", but on the client the JSX transformation produces an array of two children—one text node ("count is ") and one number (0)—and for some reason the number isn’t being reinserted during hydration.
This isn’t a bug in the JSX transform per se; instead, it’s a mismatch between how the HTML is created on the server (with adjacent text nodes coalesced) and how the client renders the resulting tree from the component function. React’s hydration algorithm sees the discrepancy as a potential bug (or symptom of different data), which is why it warns you.
While there are a couple of ways to address it, the goal is to ensure that the component produces the same node structure on both the server and the client. Two common solutions are:
Using a template literal forces the complete text to be generated in one go:
JAVASCRIPTfunction App() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> {`count is ${count}`} </button> ); }
This way, both server and client render exactly one text node with the fully concatenated string.
Alternatively, you can wrap the dynamic part in an element to keep the structure explicit:
JAVASCRIPTfunction App() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> count is <span>{count}</span> </button> ); }
This method makes it clear where the dynamic content is rendered and prevents unexpected merging of text nodes.
React isn’t “failing” its own JSX transform—instead, the server and client output aren’t matching up due to how text nodes are split versus merged. By ensuring that your component produces an identical structure in both environments (for example, by concatenating the text explicitly), you can eliminate the hydration warning.
Remember that suppressing hydration warnings (using the suppressHydrationWarning
prop) is intended as an escape hatch for unavoidable mismatches. It’s best to address the underlying cause whenever possible.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by GalacticEnvoy179 • 1 month ago
It turns out that React inserts comment nodes to support this children format, so the generated HTML should look like this instead:
HTML<div id="app"><button>count is <!-- -->0</button></div>
However, I was using node-html-parser
to generate my HTML doc, not realizing it removed comments by default. So renderToString
was in fact returning the correct content but it was stripped out on my end.
No comments yet.
No comments yet.