Shopify Theme Memory Leaks — disconnectedCallback Is Not Optional
Memory leaks in Shopify themes are invisible until they cause real problems: the Theme Editor becomes sluggish, interactions slow down over time, and the browser tab consumes growing amounts of RAM. The most common cause is missing disconnectedCallback implementations in Web Components. When a component is removed from the DOM — whether by the Section Rendering API, Theme Editor interactions, or page navigation — any event listeners, timers, or observers it created continue running and holding references to memory. This guide gives you the complete cleanup checklist.
What gets leaked when disconnectedCallback is missing
Every resource a Web Component creates that references the component persists in memory after the component is removed from the DOM, unless explicitly cleaned up. This includes: window event listeners (resize, scroll, keydown), setInterval and setTimeout timers, MutationObserver and ResizeObserver instances, IntersectionObserver instances, active fetch requests, and any reference stored in a global variable or closure that points to the component. Each leak holds the component and its subtree in memory — preventing garbage collection.
The complete disconnectedCallback cleanup checklist
1. Remove all window and document event listeners added in connectedCallback. 2. Clear all setInterval and setTimeout timers — store their IDs and call clearInterval/clearTimeout. 3. Disconnect all MutationObserver, ResizeObserver, and IntersectionObserver instances — call .disconnect() on each. 4. Abort all active fetch requests by calling abort() on stored AbortController instances. 5. Remove all references stored in global objects or closures that point back to the component. 6. Clear any data stored in external state systems (pub/sub subscriptions, custom event registrations).
Using AbortController to cancel fetch and event listeners simultaneously
AbortController is the cleanest way to manage both fetch cancellation and event listener cleanup in one mechanism. Create a controller in connectedCallback: this.abortController = new AbortController(); const { signal } = this.abortController;. Pass signal to fetch: fetch('/cart.js', { signal }). Add listeners with signal: element.addEventListener('click', handler, { signal }). In disconnectedCallback, call this.abortController.abort() — this cancels all pending fetches and removes all listeners registered with that signal simultaneously. No need to track individual listeners.
Testing for memory leaks in Shopify themes
Use Chrome DevTools Memory tab to detect leaks. Steps: 1. Open the Theme Editor and navigate to the page with your component. 2. Open DevTools > Memory > take a heap snapshot. 3. Perform the action that creates and destroys the component (add section, remove section). 4. Force garbage collection (GC button in Memory tab). 5. Take another heap snapshot. 6. Compare — if your component class still appears in the snapshot after removal and GC, you have a leak. Filter by your class name to find it quickly.
The fix: before and after
// CODE_COMPARISON
Frequently asked questions
- How often is disconnectedCallback actually triggered in a Shopify theme?
More often than you might expect. It triggers every time: a merchant uses the Theme Editor to remove or rearrange a section, Shopify's Section Rendering API replaces section HTML after a cart update, a dynamic section is added or removed via JavaScript, and in Hydrogen, on every page navigation. In active Theme Editor sessions, it can trigger dozens of times per minute.
- Does missing disconnectedCallback cause visible problems for store visitors?
On a single page view, usually not. Memory leaks accumulate over time — they affect visitors who spend extended time on a page (looking at products, customizing), merchants using the Theme Editor (which triggers many component creation/destruction cycles), and browser tabs kept open. Symptoms: increasing memory usage in Task Manager, slowing interactions after several minutes, and eventually tab crashes.
- What is the difference between AbortController and manually tracking listeners?
Manual tracking requires storing a reference to each bound handler and calling removeEventListener for each one individually in disconnectedCallback. AbortController allows you to register multiple listeners and cancel them all at once with a single abort() call. It also works with fetch(), making it the recommended pattern for Web Components that make HTTP requests. The signal option for addEventListener is supported in all modern browsers since 2021.
// 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 →