Home / Articles

DOM nodes are not the HTML string you shipped

The DOM isn't the HTML string you shipped. Parse trees, live NodeLists and reflows explain why string-level thinking breaks in JavaScript and React frontends.

Publisher: REACTAPP.TOP5 min read1180 words

This article is published in English.

Sources: The DOM Is Not Your HTML: The Difference Every Frontend Developer Should Understand

You author HTML, yet JavaScript never edits that file on disk. It talks to the browser’s live Document Object Model.

When markup ships to the browser, it establishes the initial page structure. A document might begin with a simple heading:

<h1>Hello</h1>

After load, JavaScript can locate that heading and replace its text:

document.querySelector("h1").textContent = "Hello World";

The screen then shows:

<h1>Hello World</h1>

It can look as if the HTML file itself was rewritten. It was not. The file on the server still holds the original heading. JavaScript changed the browser’s live model of the page—the DOM. Separating source markup from that live model clarifies many JavaScript behaviors.

HTML is a document. The DOM is a live representation.

HTML is the markup you author, for example:

<body>
  <h1>Hello</h1>
  <button>Click me</button>
</body>

The browser parses that document into an in-memory structure. That structure is the Document Object Model. A rough sketch of the handoff looks like:

HTML
 │
 │ browser parses
 ▼
DOM
 │
 ├── body
 │    ├── h1
 │    └── button

The DOM is not merely a string twin of your HTML. It is a structured, programmable document. That is what JavaScript manipulates.

So what does JavaScript actually change?

Start from:

<h1 id="title">Hello</h1>

Then run:

const title = document.querySelector("#title");
title.textContent = "Welcome";

The script finds the DOM node and updates its text. The live tree now represents:

<h1 id="title">Welcome</h1>

The original HTML source was not rewritten. If the markup came from a server or static file, that source still contains:

<h1 id="title">Hello</h1>

The distinction is small and critical: mutating the DOM is not the same as editing the HTML source.

Here’s an easy way to see the difference

Open a page and change something from the console:

document.querySelector("h1").textContent = "Changed!";

The visible page updates immediately. Compare the live DOM:

<h1>Changed!</h1>

with the original HTML source:

<h1>Hello</h1>

They can diverge because the browser works against its live DOM.

The DOM is more than what you originally wrote

Suppose the markup starts as:

<ul id="users">
  <li>John</li>
</ul>

JavaScript can append another item:

const list = document.querySelector("#users");
const item = document.createElement("li");
item.textContent = "Sarah";
list.appendChild(item);

The live tree now contains:

ul
├── li "John"
└── li "Sarah"

“Sarah” never appeared in the original file. The browser does not require a rewritten source document; the script mutated the live DOM. That capability underpins interactive sites.

The DOM is a tree

“Object Model” sounds heavier than the idea is. At its simplest, the DOM is a tree of nodes. Given:

<main>
  <h1>Hello</h1>
  <p>Welcome!</p>
</main>

the browser builds a structure roughly like:

Document
   │
   └── main
       ├── h1
       │    └── "Hello"
       │
       └── p
            └── "Welcome!"

That tree shape is why scripts can walk relationships such as:

element.parentElement
element.children
element.nextElementSibling

You are not scanning a plain text file; you are navigating a structured tree.

And the DOM can change

Source HTML is typically the initial document. The in-memory tree stays mutable at runtime. Scripts can allocate nodes:

const button = document.createElement("button");

set attributes:

button.setAttribute("disabled", "");

change content:

button.textContent = "Loading...";

and insert the node:

document.body.append(button);

The tree evolves while the application runs, which is why interactive interfaces do not need a full HTML download for every small change.

But there is one more thing developers often confuse

Inspecting a page can reveal HTML that was never in the original source. Script might create:

<div class="notification">
  Saved successfully
</div>

You did not author that node, yet DevTools can show it in the Elements panel because that panel reflects the DOM. View Source may omit the node entirely.

View Source and Inspect Element are therefore not guaranteed to match. One common reason is JavaScript mutating the DOM after the initial HTML load.

Why does this matter for frontend developers?

Much frontend behavior becomes clearer once the mental model shifts from “JavaScript changes the HTML” to “JavaScript changes the DOM.” That framing explains APIs such as:

element.classList.add("active");
element.remove();
element.textContent = "Loading...";
element.append(newElement);

Those calls operate on the live document structure. The browser then uses that updated tree when painting the screen.

What about React?

The same distinction helps when learning React. You might write:

return <h1>Hello</h1>;

JSX is not literally the DOM. It is syntax that describes UI. React decides how that description should be reflected in the real DOM. Understanding the DOM before diving into framework abstractions makes rendering, reconciliation, refs, and DOM updates less mysterious.

You do not need every internal detail. Keep this pipeline in mind:

HTML
  ↓
Browser parses it
  ↓
DOM
  ↓
JavaScript can modify it
  ↓
Browser updates the rendered page

Frameworks add layers on top, but the browser DOM remains the foundation.

One common mistake

Developers sometimes treat a DOM mutation as a permanent edit to the original HTML. It is not. For example:

document.body.innerHTML = "<h1>New page</h1>";

That assignment can replace the current live structure, yet it does not rewrite the HTML file on the server. A refresh reloads the document from its source. Unless the change was persisted and new HTML was generated, the original document returns. That gap separates runtime state from source content.

The mental model to remember

Avoid collapsing the ideas:

HTML = DOM

Prefer a flow closer to:

HTML
 │
 │ parsed by browser
 ▼
DOM
 │
 │ modified by JavaScript
 ▼
Updated DOM
 │
 │ used during rendering
 ▼
What the user sees

Treat HTML as the authored source document. Treat the DOM as the browser’s mutable tree built from that source. Scripts mutate the tree; painting follows whatever the tree currently holds.

Final takeaway

Do not equate the DOM with the HTML file. Markup is what you author; the object model is what the browser keeps alive while the tab is open. With that separation, several behaviors become obvious: why scripts can add and remove nodes, why querySelector() works, why textContent updates what you see, why DevTools can show nodes you never authored, why the live tree can diverge from source, and why React still ultimately touches the DOM.

The useful shift is simple: you write HTML, the browser builds the DOM, and JavaScript changes the DOM. Once that clicks, the DOM stops feeling like an opaque API and starts looking like what it is—the browser’s live representation of the page.