Never Use innerHTML += in Shopify — It Serializes the Entire DOM on Every Call
innerHTML += content appears in thousands of Shopify themes as a way to append HTML to an element. It looks harmless but is one of the most expensive DOM operations you can perform: the browser serializes the entire current subtree to an HTML string, concatenates your new content, then re-parses the entire string and rebuilds the entire DOM subtree from scratch. Every Web Component, every event listener, every reference inside that subtree is destroyed and recreated. For a cart drawer with 10 line items, this is catastrophic.
What innerHTML += actually does step by step
When JavaScript executes container.innerHTML += newHtml, the browser: 1. Serializes all existing child nodes to an HTML string (potentially thousands of characters). 2. Concatenates your new HTML string to the end. 3. Parses the combined HTML string back into DOM nodes. 4. Destroys all existing child nodes (firing disconnectedCallback on all Web Components, removing all event listeners from all elements). 5. Inserts the newly parsed nodes. This is O(n) in the size of the existing content — getting slower as more items accumulate.
Use insertAdjacentHTML for appending without DOM destruction
insertAdjacentHTML appends HTML to an existing element without touching its current content: container.insertAdjacentHTML('beforeend', newHtml). The browser parses only the new HTML and inserts it at the specified position — 'beforebegin', 'afterbegin', 'beforeend', 'afterend' — without serializing or destroying existing content. Existing event listeners are preserved. This is O(1) in the size of existing content — performance does not degrade as the list grows.
Use DocumentFragment for bulk DOM insertions
When inserting multiple elements at once, use DocumentFragment to batch DOM mutations and minimize reflows: const fragment = document.createDocumentFragment(); items.forEach(item => { const el = document.createElement('div'); el.className = 'cart-item'; el.textContent = item.title; fragment.appendChild(el); }); container.appendChild(fragment). All inserts happen in a single DOM operation, triggering one reflow instead of one per item. This is significantly faster than multiple appendChild or insertAdjacentHTML calls in a loop.
Correct patterns for replacing vs appending content
For replacing all content (like updating a cart drawer): container.innerHTML = newHtml — this replaces all content at once, which is fine. Avoid only the += concatenation pattern. For appending new items: container.insertAdjacentHTML('beforeend', itemHtml). For prepending: container.insertAdjacentHTML('afterbegin', itemHtml). For replacing a specific element in the list: existingElement.outerHTML = newItemHtml — replaces just that element without touching siblings.
The fix: before and after
// CODE_COMPARISON
Frequently asked questions
- Is container.innerHTML = newContent (without +=) also a problem?
Setting innerHTML = newContent replaces all children at once, which is efficient when you intend to replace the entire content. This is acceptable for cases like re-rendering a cart drawer after a server response. The problematic pattern is specifically the += concatenation, which grows the content iteratively while destroying and rebuilding the entire subtree on each call.
- Does using insertAdjacentHTML have any security implications?
Yes. insertAdjacentHTML parses the HTML string and executes any script elements within it. Always sanitize user-provided content before inserting it with insertAdjacentHTML or innerHTML. For user-generated text content, use textContent or createTextNode() instead — these never execute scripts and treat content as plain text.
- What is the performance difference between innerHTML += and insertAdjacentHTML in practice?
For a cart with 5 items, innerHTML += on each item add is about 5x slower than insertAdjacentHTML (because it serializes and re-parses 5x more content on the 5th call than the 1st). For a cart with 20 items, it is about 20x slower. Additionally, innerHTML += fires disconnectedCallback on all Web Components in the subtree, causing full component teardown and re-initialization — multiplying the time cost further.
// SCAN_YOUR_CODE
Does your theme have this bug?
Paste your code. Syphio automatically detects and fixes this error and hundreds of others — in seconds.
Check my JavaScript →