For as long as I've worked at CodePen, there's been on on-going issue that has really bugged me. Some members have reported that CodePen-hosted images were not returning with the correct CORS headers when they're used in Canvas or WebGL Pens. This can be a real bummer, because if an asset doesn't return with Cross-Origin enabled, the Canvas or WebGL Pen will not render.

Part of the reason this issue was so frustrating is that it seemed to happen randomly, an asset could throw a CORS error for one of our members, but it would load when I tried to use that same asset. The solution I would recommend to people when they came across this error is to add a query string to the end of their asset, essentially cache-busting the request. The thing that didn't make sense to me was why the request was caching even after I made sure to set no Expiry and 'cache-control: no-cache' on the S3 assets themselves.

One day on Twitter, Amelia Bellamy-Royds sent me a link to an article about different kinds of cache and I finally realised what had been happening.

The issue: Our Asset Manager and the Browser Memory Cache

When you upload an image on CodePen using the asset manager, we provide a handy little preview thumbnail for you to see the image you just uploaded. Unfortunately, this is where the trouble would begin for members trying to use Canvas or WebGL.

When we requested the asset as a background-image, S3 would return the request without CORS headers.

a screenshot of Chrome Devtools showing the headers for an image GET request
When a GET request is made to show the image, no CORS headers are returned (as they aren't needed)

The image request would then end up stored in the browser Memory Cache, without CORS headers. That meant when a member went to load the same image path into their Canvas or WebGL pen, the request would return without CORS headers and show an error.

a screenshot of Chrome Devtools showing the CORS error after using the same image path in an AJAX request
the (very confusing) error

This is why adding a cache-busting string to the asset caused the request to return the correct headers. The Memory Cache didn't have anything stored for the different image path, so it sent the cross-origin Ajax request to S3, which would return the headers.

A fix, hopefully.

I figured the best way to prevent our asset previews from causing the browser memory cache to store the asset URL request is to add a cache-busting string to the preview request.

That way, if you decide to use a URL like this ...

... in your Canvas or WebGL Pen, it should be a fresh request and return the correct CORS. Just be wary about using your image assets in an <img> element or as a background-image before attempting to use them via an Ajax method, as you'll likely run into a similar caching problem.

Remember, if you do see CORS behaving badly, you can always change the query string on the end of your asset URL eg. '?v=123' and this will force the browser to request a fresh version of the S3 asset.

I am hoping this change will prevent some CORS headaches (my least favorite kind of headache) for you all. Thanks again to Amelia for planting the seed of realize-cache-ion!