I have found a solution and have implemented on my current website
Warning: Only use this if you are not using shop pay.
On this file - layout/theme.liquid , there are two scripts here.
1st script above this part - {{ content_for_header }}
<script>
(function() {
// Block fetch requests to shop.app
const originalFetch = window.fetch;
window.fetch = function(url, options) {
const urlStr = typeof url === 'string' ? url : url?.url || '';
if (urlStr.includes('shop.app') || urlStr.includes('pay/hop')) {
console.warn('Blocked Shop Pay request:', urlStr);
return Promise.resolve(new Response('{}', { status: 200 }));
}
return originalFetch.apply(this, arguments);
};
// Block XMLHttpRequest to shop.app
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
if (url && (url.includes('shop.app') || url.includes('pay/hop'))) {
console.warn('Blocked Shop Pay XHR:', url);
this.\_blocked = true;
}
return originalXHROpen.apply(this, arguments);
};
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
if (this.\_blocked) return;
return originalXHRSend.apply(this, arguments);
};
// Block iframes to shop.app
const originalCreateElement = document.createElement.bind(document);
document.createElement = function(tagName) {
const element = originalCreateElement(tagName);
if (tagName.toLowerCase() === 'iframe') {
const origSetAttr = element.setAttribute.bind(element);
element.setAttribute = function(name, value) {
if (name === 'src' && value && (value.includes('shop.app') || value.includes('pay/hop'))) {
console.warn('Blocked Shop Pay iframe:', value);
return;
}
return origSetAttr(name, value);
};
}
// Also block undefined stylesheets
if (tagName.toLowerCase() === 'link') {
const origSetAttr = element.setAttribute.bind(element);
element.setAttribute = function(name, value) {
if (name === 'href' && (value === 'undefined' || value === '/undefined' || value.endsWith('/undefined'))) {
console.warn('Blocked broken stylesheet:', value);
return;
}
return origSetAttr(name, value);
};
}
return element;
};
})();
</script>
2nd script AFTER {{ content_for_header }}
<script>
(function() {
// Remove shop.app iframes and broken stylesheets
function cleanupDOM() {
// Remove shop.app iframes
document.querySelectorAll('iframe').forEach(function(iframe) {
var src = iframe.src || iframe.getAttribute('src') || '';
if (src.includes('shop.app') || src.includes('pay/hop')) {
console.warn('Removed Shop Pay iframe:', src);
iframe.remove();
}
});
// Remove broken stylesheets
document.querySelectorAll('link[rel="stylesheet"]').forEach(function(link) {
var href = link.getAttribute('href');
if (!href || href === 'undefined' || href === '/undefined' || href.endsWith('/undefined') || href === 'null') {
console.warn('Removed broken stylesheet:', href);
link.remove();
}
});
}
cleanupDOM();
// Watch for new iframes and broken links
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType !== 1) return;
// Check iframes
if (node.tagName === 'IFRAME') {
var src = node.src || node.getAttribute('src') || '';
if (src.includes('shop.app') || src.includes('pay/hop')) {
console.warn('Blocked Shop Pay iframe:', src);
node.remove();
}
}
// Check links
if (node.tagName === 'LINK' && node.rel === 'stylesheet') {
var href = node.getAttribute('href');
if (!href || href === 'undefined' || href === '/undefined' || href.endsWith('/undefined') || href === 'null') {
console.warn('Blocked broken stylesheet:', href);
node.remove();
}
}
// Also check children
if (node.querySelectorAll) {
node.querySelectorAll('iframe').forEach(function(iframe) {
var src = iframe.src || iframe.getAttribute('src') || '';
if (src.includes('shop.app') || src.includes('pay/hop')) {
console.warn('Blocked nested Shop Pay iframe:', src);
iframe.remove();
}
});
}
});
});
});
observer.observe(document.documentElement, { childList: true, subtree: true });
// Also intercept iframe.src property setter
var iframeProto = HTMLIFrameElement.prototype;
var srcDescriptor = Object.getOwnPropertyDescriptor(iframeProto, 'src');
if (srcDescriptor && srcDescriptor.set) {
Object.defineProperty(iframeProto, 'src', {
set: function(value) {
if (value && (value.includes('shop.app') || value.includes('pay/hop'))) {
console.warn('Blocked Shop Pay iframe src:', value);
return;
}
srcDescriptor.set.call(this, value);
},
get: srcDescriptor.get
});
}
// Run cleanup periodically for first few seconds
var cleanupCount = 0;
var cleanupInterval = setInterval(function() {
cleanupDOM();
cleanupCount++;
if (cleanupCount > 10) clearInterval(cleanupInterval);
}, 500);
})();
</script>
Basically:
<head>
...
<!-- 1st SCRIPT -->
<script>...</script>
{{ content_for_header }}
<!-- 2nd SCRIPT -->
<script>...</script>
...
</head>
SHOP PAY BLOCKER
Blocks: shop.app, pay/hop
Implementation:
1. Override window.fetch() - returns empty Response for blocked URLs
2. Override XMLHttpRequest.open() - sets _blocked flag, send() checks flag
3. Override document.createElement() - intercepts iframe/link setAttribute()
4. Override HTMLIFrameElement.prototype.src setter - blocks src assignment
5. MutationObserver on document.documentElement - removes blocked iframes/links
6. setInterval cleanup every 500ms x 10 iterations
Pattern matched: url.includes('shop.app') || url.includes('pay/hop')