Some apps change the execution order of app block js script

Some SEO or optimization apps change the execution order of my app block script, which prevents my app block from running normally.

These apps change the theme.liquid file and I have no way to stop it except manually code change. Previously, it’s the Avada SEO app. The latest one is Page Speed Optimizer.

Why does Shopify allow this? How can an app developer deal with this issue?

2 Likes

Hey Benny - when you say your app doesn’t run normally, what specifically is breaking for your app? Will look into these cases for you on my side too.

I can give an example. Let’s say I define a variable in my app-block.liquid:
var handle = "{{ handle }}";

In the schema of the liquid file, I add the js file:

{% schema %}
  {"
    "stylesheet": "my_app.css",
    "javascript": "my_app.js",
...

In my_app.js, I cannot get the handle variable. It’s undefined. I wait the js and CSS to be loaded with

window.addEventListener('DOMContentLoaded', function () {
  console.log('handle = ', handle)
})

But the handle is undefined here. This is the liquid file added to the <head> of theme.liquid by the optimization app. It checks the script tags and disables them. I think this approach is destructive:

{% assign expiry = '2025-01-10' | date: '%s' %}{% assign current_date = 'now' | date: '%s' %}{% if current_date < expiry %}<script>var _listeners=[];EventTarget.prototype.addEventListenerBase=EventTarget.prototype.addEventListener,EventTarget.prototype.addEventListener=function(e,t,p){_listeners.push({target:this,type:e,listener:t}),this.addEventListenerBase(e,t,p)},EventTarget.prototype.removeEventListeners=function(e){for(var t=0;t!=_listeners.length;t++){var r=_listeners[t],n=r.target,s=r.type,i=r.listener;n==this&&s==e&&this.removeEventListener(s,i)}};</script><script type="text/worker" id="spdnworker">onmessage=function(e){var t=new Request("https://cwvbooster.kirklandapps.com/optimize/437",{redirect:"follow"});fetch(t).then(e=>e.text()).then(e=>{postMessage(e)})};</script>
<script type="text/javascript">var spdnx=new Worker("data:text/javascript;base64,"+btoa(document.getElementById("spdnworker").textContent));spdnx.onmessage=function(t){var e=document.createElement("script");e.type="text/javascript",e.textContent=t.data,document.head.appendChild(e)},spdnx.postMessage("init");</script>
<script type="text/javascript" data-spdn="1">
const observer=new MutationObserver(mutations=>{mutations.forEach(({addedNodes})=>{addedNodes.forEach(node=>{
if(node.tagName==='SCRIPT'&&node.innerHTML.includes('asyncLoad')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('hotjar')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('xklaviyo')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('recaptcha')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('klaviyo')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('shop.app')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('chat')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('consent')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('apps')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('extensions')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('judge.me')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('gorgias')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('perf')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('facebook.net')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('gorgias')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('stripe')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('mem')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('privy')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('incart')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('webui')){node.type='text/spdnscript'}
 if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('gtag')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('tagmanager')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.innerHTML.includes('gtm')){node.type='text/spdnscript'}
if(node.nodeType===1&&node.tagName==='SCRIPT'&&node.src.includes('googlet')){node.type='text/spdnscript'}
if(node.tagName==='SCRIPT'&&node.className=='analytics'){node.type='text/spdnscript'}  })})})
var ua = navigator.userAgent.toLowerCase();if ((ua.indexOf('chrome') > -1 || ua.indexOf('firefox') > -1) && window.location.href.indexOf("no-optimization") === -1 && window.location.href.indexOf("debug") === -1 && window.location.href.indexOf("cart") === -1 ) {observer.observe(document.documentElement,{childList:true,subtree:true})}</script>{% endif %}

Which app specifically is this snippet from?

That code is from Speedien though I no longer see that particular in the app store. I think they are called Page Speed Optimizer LCP & CLS now. I’ve run into issues with them in the past.

I will add that I’ve had other SEO apps remove my code using JavaScript as well. Below is another example from Webrex where the app removes code (and this is the default behavior).

  <!-- BEGIN app snippet: removeScript --><script id="ws_json_ld_script" type="module">
  const wsSeoUrlParams=new URLSearchParams(location.search);let wsSeoTestParam=wsSeoUrlParams.get("seoJsonDisabled");if(void 0===window.ws_script){if(window.ws_script=!0,!wsSeoTestParam||wsSeoTestParam&&"true"!=wsSeoTestParam){let e=()=>{document.querySelectorAll('[type="application/ld+json"]').forEach(e=>{"ws_schema"!=e.className&&e.remove()})};e(),setInterval(e,1e3)}else document.querySelectorAll('[type="application/ld+json"]').forEach(e=>{"ws_schema"==e.className&&e.remove()});document.querySelectorAll("[itemscope]").forEach(e=>e.removeAttribute("itemscope"))}else document.getElementById("ws_json_ld_script").remove();
</script>
<!-- END app snippet -->

In the script above, they are removing all structured data from the theme and apps so that only their structured data remains.

After reporting it to Shopify on multiple occasions and never hearing back, I started telling the merchants about their bad behavior. It’s really shady to disable another app’s code, especially if the merchant isn’t aware of it.

Its frustrating though because merchants think our apps don’t work when really these other apps are preventing them from working and there is nothing we can do.

1 Like

@Ilana_Davis

Thanks for your reply. You can manually hide the script from the theme.liquid file, e.g.

{% unless YOUR APP %}
  {% render spdn %}
{% endunless %}

If you have theme access (WRITE), your app can check if the theme.liquid file contains the line of spdn and overwrites it with the unless code. This approach is automated

The manual change takes time for each user and I find the issue disappointing.

I don’t have write theme access nor do I want to get write access.

Manually changing the code becomes a game of who edited the theme code last, and it puts the merchant in a difficult situation because they don’t know what’s right or wrong.

I am not in the habit of editing other apps code much like I don’t want any other app to modify mine. However, I get that you do what you have to do.

I could also write a similar script that would block other apps code so that only mine shows, but that’s just crappy for the whole ecosystem and creates a war against other apps.

Ultimately, I wish Shopify would not allow apps to modify code using JavaScript like this.

I can confirm that this issue is escalating. Recently, a store approached us because their bundle functionality was malfunctioning. Upon investigation, we discovered two problematic behaviors from different apps:

  • One app had overridden the native window.fetch function, attempting to modify the request body but failing in the process
  • Another app added an event listener to buttons on the page during the capture phase, preventing the default action and stopping immediate propagation, effectively nullifying any other listeners attached to the same button

Such practices by apps severely degrade the merchant experience and should be prohibited.

Hi @Anton - can you use this form to log this issue with our internal team who investigates these? Report a partner violation - Shopify