Using Respond.js with Shopify: Redux

Since describing how to get Respond.js to work with Shopify, plenty of folks have been in touch with questions, comments and a few issues. In this post, I’ll respond to some common questions, and describe a technique to make the original trick even easier.


First things first: this article is a follow-up, rather than a replacement. That means that if you haven’t read the original post, you should probably do that now and check that you understand what’s going on.

After that, come on back here and we’ll see how to:

  • solve common issues that crop up with the original technique;
  • implement the technique without having to use a page.respond.liquid template.

Solutions to Common Problems

1. Check Include Order

This might seem like an obvious one, but a number of people have just copied something down wrong, or hit the wrong key on their editor and mixed up a line or two. Check that you’re including the resource references in the right order – that is, like this:

<!--[if lt IE 9]>
<script src="{{ 'js-respond-min.js' | asset_url }}"></script>
<link href="{{ 'respond-proxy.html' | asset_url | split: '?' | first }}" id="respond-proxy" rel="respond-proxy" />
<link href="//{{ shop.domain }}/pages/respond-js" id="respond-redirect" rel="respond-redirect" />
<script src="//{{ shop.domain }}/pages/respond-js" type="text/javascript"></script>
<![endif]-->

2. Check Respond.js Version

There have been a couple of updates to the Respond.js library on GitHub since my original article. These have fixed a couple of issues, notably one occuring when responsive @media queries contained a space in the breakpoint dimensions.

Just check that you’ve got the latest versions of the respond.min.js, respond.proxy.js and respond-proxy.html files from GitHub.

3. Test in Real Browsers

Because the browsers targeted by Respond.js are by definition old (IE 6-8, Firefox < 3.5), it’s not common for developers to have them lying around. That means that we often have to emulate these old browsers using software or the like.

A number of “it’s not working” issues that I’ve come across have been tracked down to using simulators that don’t accurately represent the target browser environment, rather than there being any actual problem with the code or technique. This includes using “legacy IE modes” in newer versions of Internet Explorer.

It’s always best to test on a real-world machine – if that’s not possible (I certainly find it hard), then try to use an online framework that makes “real” VMs of operating systems and browsers available. I recommend BrowserStack as an excellent way to do this.

4. MIME Type Matching Errors

This isn’t really a problem with the implementation, but rather a concern I’ve had a couple of people raise. If you’ve implemented this workaround, you may see a “Refused to execute script… strict MIME type checking is enabled.” error message in your console:

The “strict MIME type checking” error in the Chrome developer console.

If you do see, this, don’t worry! Strict MIME type checking is a feature enabled by default in many modern browsers that prevents the execution of scripts when they’re served with a MIME type other than application/javascript.

However, it’s not a concern for us, as the browsers that Respond.js is targeting (Internet Explorer <= 9, mainly) don’t implement this feature and will happily parse your text/html as script.

If you are seeing this error though, it’s a sign that your conditional comments may not be working - you may want to double check them.

Using Respond.js without a Page Template

One of the more annoying limitations of my original fix was that in order to get things to work, the template designer had to create a page template called page.respond.liquid, then create a page in Shopify’s Admin section that used that template. This isn’t ideal for a couple of reasons:

  • it clogs up the Shopify Admin with an unnecessary and potentially confusing page template option, as well as adding an otherwise-unused entry to the “Pages” list; and
  • it requires additional work outside the uploading of a template to get it set up.
The offending content page, clogging up our admin panel.

I started thinking in earnest about solving this problem after this comment on the Shopify forums emphasised the difficulties of latter point – if you’re selling a theme, it’s hard to ask purchasers to manually set this fix up.

As one of the aims of my Bootstrap for Shopify framework is to make things easy for first-time theme builders, and make it practical for folks to design and sell themes based on the framework, this seemed like a problem worth tackling, even though there are quite a lot of restrictions around content and assets on Shopify’s platform.

Fortunately, the old adage “constraint breeds creativity” rang true this time – I’d have to say this solution is one of more of my more inventive hacks!

A Quick page.respond.liquid Refresher

Why do we even need to create a page.respond.liquid template again? It’s because in order to use Respond.js on a site where stylesheet assets are hosted on a CDN (such as Shopify sites), we need to use a Cross-Domain Proxy (CDP). This, in turn, requires us to:

  • serve the core Respond.js script from the same domain as our assets (in our case, the Shopify CDN); and
  • serve the cross-domain Respond.js script from the same domain as our page (in our case, the domain our stores are hosted on, eg your-site.myshopify.com).

The first requirement isn’t a problem - we just add the core script as a regular Javascript file in the theme’s assets folder, link to it using the asset_url Liquid filter, and away we go.

The second requirement is more difficult, because the only content that is actually served from the domain a Shopify store is located on is HTML content - the pages of your site. My original fix took advantage of the fact that most browsers will treat any content loaded through the src attribute on a <script> tag as Javascript, even if the server serves it with a Content-Type: text/html header.

The original solution, then, found a way to serve the cross-domain Respond.js script as a content page - by setting the contents of a page.respond.liquid template to the Javascript content and creating a page that utilised this template.

Finding an Alternative Page

In order to overcome the limitations of this approach, outlined above, it’s necessary to find an alternative way to serve the Respond.js cross-domain script. The problem is that Shopify is extremely prescriptive about the HTML pages you’re allowed to serve – you can’t just create a respond.liquid file, drop it into the templates folder, and request the content from http://your-site.myshopify.com/respond.

Sites that weren’t utilising one of the available content pages could use that to serve the script – for example, if your store only allow guest checkout, and therefore didn’t use any of the customer account pages, one could just place the contents of the Repond.js cross-domain script into templates/customer/login.liquid and link to it like this:

...
<link href="//{{ shop.domain }}/account/login" id="respond-redirect" rel="respond-redirect" />
<script src="//{{ shop.domain }}/account/login" type="text/javascript"></script>
...

However, this is obviously not ideal. At some point, we’ll want to implement this on sites that do want to utilise customer account pages, and this approach will get in the way. The same goes for any of the other template pages provided by a Shopify theme – we want to be able to serve the cross-domain script from our shop’s domain, but not in a way that could interfere with the provision of any of Shopify’s store features.

At first, I wasn’t sure that this was possible. All of the templates Shopify allows you to modify obviously map to pages that are useful for something, whether in the customer account area or elsewhere. At first glance, then, we’re out of luck. There’s also no way to set up some sort of trickery with a query parameter either (requesting something like //{{ shop.domain }}/account/login?respond=1 when we want the Respond.js script returned, or the regular login page returned otherwise), because Shopify doesn’t generally give us access to query parameters inside Liquid templates.

Using search.liquid to Serve Dynamic Content

Fortunately for us, there is one page on a Shopify store that is required by its very nature to respond to URL query parameters. It’s the search page, which not only responds dynamically to requests, but gives us access to the query parameter inside the search.liquid template. Using this fact means that we’re able to serve content from the shop’s base domain conditionally, by doing something like this:

{% if search.terms == "my-secret-string" %}
  ... Respond.js cross-domain script ...
  {% layout none %}
{% else %}
  ... regular search.liquid template here ...
{% endif %}

We can then link to this from the <head> section of our theme.liquid like so:

...
<link href="//{{ shop.domain }}/search?q=my-secret-string" id="respond-redirect" rel="respond-redirect" />
<script src="//{{ shop.domain }}/search?q=my-secret-string" type="text/javascript"></script>
...

There you go! If you implement things this way, there’s no need to keep a page.respond.liquid around, and your Respond.js implementation will be working on Shopify straight out of the box, without requiring any setup of a Page that implements a particular template.

To make things easier and more maintainable, I’ve moved the content of the Respond.js cross-domain script into its own snippet (snippets/respond.liquid). I’ve also just used the MD5 hash of the store name as the “secret key” - it makes the solution more portable (and it’s pretty unlikely that folks will be searching for eg “9e0c34812e24e45e8be3f2cd06a57e52” on your store!) This is more or less what the key parts of my templates look like:

{% assign respond_js_secret_key = shop.domain | md5 %}
    {% unless search.terms == respond_js_secret_key %}
      ... regular search template HTML and Liquid here ...
    {% else %}
      {% include 'respond' %}
      {% layout none %}
    {% endunless %}
The `search.liquid` template.
...
    {% assign respond_js_secret_key = shop.domain | md5 %}
    <link href="//{{ shop.domain }}/search?q={{ respond_js_secret_key }}" id="respond-redirect" rel="respond-redirect" />
    <script src="//{{ shop.domain }}/search?q={{ respond_js_secret_key }}" type="text/javascript"></script>
    ...
The <head> section of the `template.liquid` layout template.

So, there you go – a way to implement Respond.js exclusively in your theme files, without resorting to using content page templates. If you’d like to see it in action, check out the demo store for Bootstrap for Shopify.


Update: The original article here used {{ shop.url }} which could cause cross-protocol HTTPS issues. I’ve corrected this to the protocol-independent URL //{{ shop.domain }}. Thanks to @sinak for pointing this and a couple of other errors out!

Update #2: @iamkeir helpfully pointed out that if you want to use matchMedia in your site outside of Respond.js, you might be best off to extract the polyfill that ships with Respond.js code and add it in a separate request.

This is because while media queries are supported by IE 9 and some mobile browsers, matchMedia isn’t and you’re going to need a polyfill for that. There are a couple of approaches you can take - I’d recommend placing the matchMedia polyfill into its own conditional comment targeting IE <=9, while leaving the Respond.js comments targeting IE <= 8.