skip to content

A server-rendered React component renders a <div> inside a <p>. The markup uses no dates, randomness or browser APIs, yet hydration still reports a mismatch. What causes it?

level: middleimportance: should knowfreq 44%

answer

  1. the parser rewrites illegal markup
  2. <p> takes phrasing content only
  3. paragraph implicitly closed by the div
  4. structural diff, not a text diff
  5. client-only rendering never showed it

basics

~20 s

The browser's HTML parser cannot nest a <div> inside a <p>, so it closes the paragraph early and makes the div a sibling. React then hydrates against a DOM whose shape differs from the tree it is rendering, and reports a mismatch.

solid answer

~50 s

React serialized exactly what you wrote, but HTML has content-model rules the parser enforces while reading the string. A `<p>` cannot contain flow content like `<div>`, so on encountering the `<div>` the parser implicitly closes the paragraph — the DOM ends up with an empty `<p>` followed by a sibling `<div>`, not a nested one. During hydration React walks the real DOM expecting the div to be the paragraph's child, finds a different structure, and reports a hydration error, then client-renders that subtree. The same class of bug comes from anything the parser rewrites: text or a `<div>` placed directly inside `<table>` gets foster-parented out, a `<tr>` written without a `<tbody>` gets one inserted, and nested `<a>` or `<form>` elements are unwound. The fix is to make the markup valid — React also flags illegal nesting with a development warning when the element renders, which usually points straight at it.

go deeper

for a junior

Know that HTML restricts what can go inside what — a <div> cannot live in a <p> — and that React warns about illegal nesting in development.

for a middle

Explain that the browser parser implicitly closes the paragraph, so the DOM shape differs from React's tree, and name a second example such as table foster parenting or the implicit <tbody>.

for a senior

Recognise this from the diff signature — structural, not textual — and trace it through indirect nesting where a shared component's root element is the illegal child. Explain why client-only rendering hid it.

for a principal

Treat HTML validity as a correctness requirement once SSR is on: make nesting warnings fail the build, and set a convention that shared components declare the element they render at the root so composition cannot produce illegal trees.

## The mismatch that has nothing to do with your data Most hydration mismatches come from the server and client computing different values. This one is different: both renders produce the *identical* React tree. The divergence happens in between, in the browser's HTML parser. ## HTML has a content model, and the parser enforces it HTML is not a generic tree format. Elements declare what they may contain, and the parsing algorithm repairs violations rather than erroring. The rule that bites here: `<p>` accepts only phrasing content — text, `<span>`, `<a>`, `<em>` and friends. A `<div>` is flow content. When the parser is inside an open `<p>` and meets a `<div>` start tag, it implicitly closes the paragraph first. So the server sends: ```html <p>Intro <div>Details</div></p> ``` and the browser builds: ```html <p>Intro </p><div>Details</div><p></p> ``` The div is now a *sibling* of the paragraph, and there is a stray empty paragraph from the orphaned closing tag. No warning is shown to the user; this is defined recovery behaviour, and it happens before any JavaScript runs. ## Why that breaks hydration specifically Hydration adopts existing DOM. React walks the nodes the parser produced, matching each against the element it is currently rendering. It renders `<p>` and looks for the paragraph's first child to match its `<div>` — and finds the paragraph closed, with the div outside. The structures do not line up, so React reports a hydration error and falls back to client-rendering that subtree, discarding the server HTML there. Client-only rendering hides this bug entirely, which is why it so often appears the day a team turns on SSR: `createRoot` builds the DOM by calling `appendChild`, and the DOM API does *not* enforce the content model. A div appended into a paragraph programmatically simply stays there. Only the parser rewrites markup, and the parser only sees the server's HTML string. ## The family of parser rewrites to know - **`<div>` (or any flow content) inside `<p>`** — paragraph implicitly closed. - **Table foster parenting.** Text or a `<div>` placed directly inside `<table>` outside a cell is moved *before* the table in the DOM. - **Implicit `<tbody>`.** Writing `<table><tr>…</tr></table>` gets a `<tbody>` inserted around the rows, so React's tree and the DOM differ by one level. - **Nested `<a>`** — an anchor inside an anchor is unwound; the inner one becomes a sibling. - **Nested `<form>`** — the inner form element is dropped. - **`<li>` outside a list, `<td>` outside a row** — relocated or discarded. All of them share the signature: a mismatch whose diff shows a *structural* difference — an element in the wrong place or an unexpected extra node — rather than differing text. ## Diagnosing it fast React validates DOM nesting in development and warns when the element renders, with a message naming both elements — that a `<div>` cannot appear as a descendant of `<p>`. That warning usually fires *before* the hydration error and identifies the component, so read the earliest message in the console, not the loudest one. A second confirmation: inspect the element in DevTools and compare the live DOM with the HTML in the network response's document — if they differ structurally before hydration, the parser did it, not your code. An especially sneaky variant is indirect nesting: a `<Card>` renders a `<div>` at its root and someone drops `<Card>` inside a `<p>` several components away. The illegal nesting is invisible in either file on its own, which is why the development warning that names the ancestor chain is worth reading carefully. ## Fixing it Fix the markup, not the symptom. Use a `<div>` or a `<span>` wrapper as the content model allows: if the child is inline, `<span>` is legal inside `<p>`; if the child is a block, the parent should not have been a paragraph. Do **not** reach for `suppressHydrationWarning` here — it silences reports about an element's own text and attributes, it does not make an illegally nested tree hydrate, and the underlying DOM would still be shaped differently from what your components describe, which quietly breaks styling and refs too. The general lesson worth stating in an interview: with SSR, invalid HTML stops being a validator nag and becomes a correctness bug, because the server's string and the client's tree must agree exactly.

  • Why does this bug appear only after a team enables server rendering, when the same components rendered fine client-side for months?
    Client rendering builds the DOM through the DOM API, which does not enforce HTML's content model — a div appended into a paragraph stays nested. The rewrite only happens when a browser *parses* markup, and only SSR produces markup to parse. So the illegal nesting was always there; SSR is what made the DOM and the React tree disagree.
  • Would suppressHydrationWarning fix an illegal-nesting mismatch?
    No. It suppresses reports about an element's own text and attributes one level deep; it does not reconcile a structurally different tree. The parser will still have moved the node, so refs, styling and event delegation act on a DOM shape your components did not describe. The only real fix is valid markup.
  • How do you catch these before they reach production?
    Keep React's development DOM-nesting warnings loud — treat them as errors in CI-run component tests rather than console noise — since they fire at render time and name both elements. Encourage components to document their root element type so a wrapper's `<div>` root is not silently dropped inside a paragraph, and check the earliest console message when a hydration error appears.

saying these in an interview costs you the question

  • Blames React's diffing rather than the HTML parser
  • Suggests suppressHydrationWarning as the fix for illegal nesting
  • Says the server sent malformed HTML that React should have escaped
  • Thinks the DOM API enforces the same nesting rules as the parser
  • Claims a <p> may contain any element as long as it is closed

context