JavaScript Rendering
Every frontend framework is, at its core, a system for deciding when and how to update the DOM. That sounds narrow, but it turns out to be nearly everything: the architecture, the performance profile, and the way a page appears to crawlers and search engines all follow from how JavaScript builds and modifies the document. This guide traces that path, from how a script gets into the browser to how modern rendering patterns keep updates efficient.
This sits between the mechanics of the browser rendering pipeline and the strategy decisions in CSR versus SSR. It also underpins the crawlability concerns in JavaScript rendering and indexing.
What you'll learn
Script loading and parse blocking
Before JavaScript can build anything, it has to get into the browser without stalling the page. How you load a script determines when it runs and how much it delays first paint.
defer, async, and the parser
<!-- Blocks the HTML parser until downloaded + executed -->
<script src="app.js"></script>
<!-- Downloads in parallel; executes after HTML parsed -->
<script src="app.js" defer></script>
<!-- Downloads in parallel; executes as soon as ready -->
<script src="app.js" async></script>
A plain <script> tag halts HTML parsing: the browser stops building the DOM, fetches the file, executes it, then resumes. defer lets parsing continue and runs the script after the document is parsed, in order; async also lets parsing continue but runs the script as soon as it downloads, in any order. For application bundles, defer is almost always the right choice: it keeps the parser moving, preserves execution order, and avoids the risk of a script running before the elements it needs exist.
Direct DOM manipulation
The oldest and most transparent rendering approach is also still the most explicit: write directly to the DOM when something changes.
Targeted updates without a framework
// Backbone view responding to a model change
var UserView = Backbone.View.extend({
initialize: function () {
this.listenTo(this.model, 'change:name', this.updateName);
},
updateName: function () {
this.$('.user-name').text(this.model.get('name'));
}
});
Backbone.js uses this pattern directly: a view listens to model events and updates the specific DOM nodes that changed. When the scope of an update is small and well-defined, as it usually is in a well-structured Backbone app, this is extremely efficient. The cost is that the developer has to manage granularity explicitly; a naive approach that re-renders the entire view on any change produces unnecessary DOM work.
Template-based rendering
Rather than writing individual DOM operations, template rendering produces a complete HTML string from current state, then sets it into a container. Simple to reason about, but potentially expensive if the container is large.
Full re-render vs partial update
render: function () {
// Re-renders the whole view from the template
this.$el.html(this.template(this.model.toJSON()));
return this;
}
A full template re-render is easy to implement and guarantees the DOM matches the current state. Its downside is that it throws away and recreates every child element, losing focus, scroll position, and any local state those elements held. This is fine for small sections, but for a large list or a complex form it is visually disruptive and computationally wasteful.
The virtual DOM model
The virtual DOM is the mechanism most modern frameworks use to reconcile the simplicity of full re-renders with the efficiency of targeted updates.
An in-memory mirror of the real DOM
A virtual DOM is a lightweight JavaScript object tree that describes the desired DOM. When state changes, the framework renders a new virtual tree, compares it to the previous one, and applies only the differences to the real DOM. From the developer's perspective, every render is a clean pass over current state. From the browser's perspective, only the changed nodes are touched. The comparison step, the diff, has a cost, so the virtual DOM is not universally faster than direct manipulation; it is faster when the set of actual DOM changes is small relative to the complexity of the view, which is most of the time in a data-driven UI.
Reconciliation and keys
Diffing two arbitrary trees is expensive, so frameworks use heuristics to keep the comparison fast, and keys are the most important one.
Helping the diff algorithm with keys
// Without keys, reordering a list destroys and recreates nodes
items.map(item => `<li>${item.name}</li>`).join('')
// With keys, the framework can move nodes instead of recreating them
// (React-style for illustration)
items.map(item => <li key={item.id}>{item.name}</li>)
When a list re-renders, the framework needs to match new nodes to old ones. Without a key, it assumes position, so inserting at the front destroys and recreates every item. With a stable key like a database ID, the framework can move, update, or remove only the nodes that actually changed. The rule is to use stable, unique keys from your data, never array indices on a list that can be reordered, because index-as-key breaks the matching and causes the same unnecessary work as no key at all.
Rendering patterns today
Direct manipulation, template rendering, and virtual DOM are not mutually exclusive; they represent a spectrum, and modern apps combine them deliberately.
Choosing the right tool for the update
Backbone uses direct manipulation by design: it is transparent, requires no abstraction layer, and forces the developer to be explicit about what changes. React and Vue use virtual DOM as their default. Svelte compiles away the virtual DOM and emits targeted DOM operations at build time, similar to hand-written Backbone updates but generated from a declarative syntax. The choice is not about which is fastest in a benchmark; it is about which matches the update granularity and team discipline of your application. A Backbone app with disciplined, targeted view renders can outperform a React app that re-renders large subtrees carelessly. The rendering pattern is a tool; the discipline is the skill. The next guide examines where rendering happens, in the browser, on the server, or ahead of time, and what each choice means for users and crawlers.
Frequently Asked Questions
Why does JavaScript block HTML parsing?
A classic script tag without defer or async pauses the HTML parser until the script downloads and executes, because the script might call document.write or otherwise change the document structure. Using defer or async lets parsing continue while the script loads.
What is the virtual DOM?
A virtual DOM is an in-memory representation of the real DOM. When state changes, the framework computes a new virtual tree, diffs it against the previous one, and applies only the changed nodes to the real DOM, reducing expensive full-page updates.
What is reconciliation?
Reconciliation is the process of comparing a new virtual DOM tree to the previous one to find the minimal set of real DOM changes needed. Frameworks use keys to match list items across renders and avoid destroying and recreating unchanged nodes.
How does Backbone.js handle DOM updates?
Backbone views listen to model and collection events and call render methods directly, updating specific parts of the DOM without a virtual DOM layer. This is the direct manipulation pattern: efficient when updates are targeted, but requires the developer to manage granularity explicitly.
Read next: the Rendering hub, or continue to the rendering lifecycle to see how these updates flow through the browser's frame pipeline.
See direct DOM rendering in practice.
The Backbone guide shows targeted view rendering applied to real models and collections.
Explore the Backbone Guide →