I'm working on a custom JavaScript setup for a Shopify development store. I use Vite to bundle everything into a single JS file that gets included via:
{{ 'bundle.js' | asset_url | script_tag }}
The Problem On some page loads, all logs and functionality behave as expected.
On other loads (without code changes), my module logs still appear (showing the script ran), but core functionality breaks silently.
Most commonly, event listeners like click and change don’t fire, or DOM-dependent code fails to execute.
This only happens sometimes, seemingly depending on when and how Shopify renders DOM elements, or how third-party apps modify them.
I've wrapped my initialization in DOMContentLoaded, but it doesn't reliably fix the issue.
Tried Solutions
- Removing Vite entirely and using plain tags – same issue.
- Switching to import maps – no improvement.
- Adding a version parameter to the script URL (?v=123) and dynamically parsing it to bypass Shopify caching – no effect.
Example 1: Overall JS Initialization Logic I initialize many features and utilities from different modules, like so:
import { initA, initB } from './module-a.js';
import { initC, initD } from './module-b.js';
import { setupLightbox } from './lightbox.js';
const initialize = () => {
initA();
initB();
initC();
initD();
if (window.location.href.includes('custom-config')) {
initCustomFeature();
}
};
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM loaded');
initialize();
setupLightbox({
selector: '.lightbox',
loop: true,
touchNavigation: true,
});
});
Even though DOMContentLoaded fires and logs appear, some modules fail to work properly — especially ones relying on DOM elements added by Shopify sections or apps.
Module with Click + Change Event Listeners This is a simplified version of a module where the issue appears:
export const exampleFeature = {
initialize() {
this.sizeContainer = document.querySelector('.size-options');
this.countSelector = document.querySelector('.count-selector');
this.setupEventListeners();
this.updateCountOptions('4');
},
setupEventListeners() {
this.sizeContainer?.addEventListener('click', (e) => {
const selected = e.target.closest('.size-option');
if (!selected) return;
document.querySelectorAll('.size-option').forEach((el) => el.classList.remove('active'));
selected.classList.add('active');
this.updateCountOptions(selected.dataset.size);
});
this.countSelector?.addEventListener('change', (e) => {
const value = e.target.value;
this.updateSelectionUI(value);
});
},
updateCountOptions(size) {
// Logic to update count options
},
updateSelectionUI(value) {
// Update UI or internal state
},
};
This module often fails silently: either the event listeners don’t fire, or the DOM elements return null when selected — despite DOMContentLoaded supposedly guaranteeing that the DOM is ready.