HTML validation, templating and frameworks
Doctypes, validator errors worth acting on, how JSX and template languages map to the DOM, escaping and hydration.
Validating the document
A validator parses your document the way a browser does and reports where it had to guess. Since the parser silently repairs almost everything, invalid markup does not crash — it becomes a slightly different tree than the one you wrote, which is how bugs hide.
<!-- invalid: a block element cannot live inside a paragraph -->
<p>Read the <div>setup guide</div> first.</p>
<!-- valid: the parser keeps the structure you intended -->
<p>Read the <a href="/guides/setup/">setup guide</a> first.</p>| Validator message | Typical cause | Fix |
|---|---|---|
| Stray end tag | Extra or mismatched closing tag | Match tags in reverse order, remove the duplicate |
| Element not allowed as child of element | Block inside <p>, <div> inside <ul> | Use the child the parent permits, or change the parent |
| Duplicate ID | Two elements share an id | Make ids unique; use classes for styling hooks |
| Attribute not allowed on element | A property from another element guessed at | Check the element's attribute list |
| Source not allowed without captions | A <track> is missing | Add a captions track to the media element |
| Bad value for attribute | Unquoted or malformed value | Quote the value and check its syntax |
div pretending to be a button, passes cleanly and still fails real users.How template languages map to the DOM
JSX, single-file components, Jinja and Handlebars all end up producing the same thing: DOM nodes. What differs is the syntax and the escaping defaults, and those are exactly where the sharp edges are.
// template source
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
// rendered DOM
<ul><li>Widget</li><li>Gadget</li></ul>- Reserved-word attributes get renamed:
classbecomesclassNameandforbecomeshtmlForin JSX. - Void elements must be self-closed in JSX (
<img />), because the parser there is JavaScript, not HTML. - Interpolation is escaped by default;
dangerouslySetInnerHTMLandv-htmlopt out of that protection and must never receive user input. - Building markup by string concatenation re-opens the escaping problem on every release, including the closing-script sequence problem when embedding data into a page.
Escaping and hydration
Server rendering sends finished HTML, and the client then takes over the same tree — a process called hydration. It only works when both sides produce identical markup; a mismatch forces the framework to throw away the server's version of that subtree and re-render it, which is a visible flicker and a wasted pass.
// server renders 12:00:04, client renders 12:00:05 — mismatch
<p>{new Date().toLocaleTimeString()}</p>
// stable markup first, live value after mount
const [now, setNow] = useState(null);
useEffect(() => setNow(new Date()), []);- Anything nondeterministic during the first render will mismatch: current time, random values, locale-dependent date formatting and browser-only APIs.
- Rendering
nullon the server and the real value in an effect is the standard fix. - Read framework warnings seriously — they name the exact element that differs.
- Escaping output prevents injection, but it does not make stored HTML safe: sanitise rich text on the server before it is ever rendered.
FAQ
Does passing the validator improve my search ranking?
Why does the validator complain about an ampersand in my URL?
& starts an entity reference in HTML. Write it as & inside attribute values and text.Related
The document head: metadata, favicons and social cards Accessibility foundations in markup
Last refreshed 2026-09-18.