Serving ActiveStorage attachments through a CDN

Serving ActiveStorage attachments through a CDN

Here at FastRuby.io we always try to have our own applications in such a state that they can always be pointed to as models in terms of performance and accessibility.

One of the tools we use to achieve that is our CDN. After all, considering we have clients everywhere from the US to New Zealand, we want anyone perusing our websites to have good loading times for the pages and, especially, the assets.

However, no matter how thorough, one always misses a spot or two.

In our case, a few of our meta tags, responsible for providing the images that appear in our social media postings were using S3 links to where the images were actually stored.

Well, not really. These were the links given by ActiveStorage, which are temporary by default, but the point is, it’d be better if these images were served via CDN because then:

  1. Their links would always be publicly accessible
  2. They’d be served with similar loading times wherever you might be in the world
  3. Links would no longer be temporary

So the question was precisely: how do we get these images to be served via our CDN?

Luckily for us, ActiveStorage also provides us with a solution.

ActiveStorage proxy mode

To do this, we need to generate URLs with proxy mode opens a new window . What this means is that, when some client requests the attachment, our application will download the image from the storage service and serve it. Note that this is something separate from using a CDN with your app. It’s just a way to configure ActiveStorage. You could configure it this way and let your app download attachments on every request. There might be use cases for that. But because we want to use a CDN we must configure ActiveStorage in proxy mode.

We also need to decide wether our app will use proxy mode for all attachments by default or only use it for specific attachments via the URL helpers rails provides (rails_storage_proxy_path and rails_storage_proxy_url).

In our case, because we want to only serve specific attachments via a CDN, we need to use the URL helper approach and manually configure these URLs to use the CDN host instead of the application host.

This way, we get all the caching benefits of proxy mode and the availability of the CDN.

Below is an example configuration, which, incidentally, was precisely what we ended up using as it served our purposes just fine:

# config/routes.rb
direct :cdn_image do |model, options|
  expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }

  if model.respond_to?(:signed_id)
    route_for(
      :rails_service_blob_proxy,
      model.signed_id(expires_in: expires_in),
      model.filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  else
    signed_blob_id = model.blob.signed_id(expires_in: expires_in)
    variation_key  = model.variation.key
    filename       = model.blob.filename

    route_for(
      :rails_blob_representation_proxy,
      signed_blob_id,
      variation_key,
      filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  end
end

This should generate routes like:

<%= cdn_image_url(user.avatar.variant(resize_to_limit: [128, 128])) %>

Now, we only need to use these helpers in the meta tags we want. In our case, there were three meta tags that served attached images used in our social media postings and for SEO purposes.

So where we had this:

<meta name="twitter:image:src" content="<%= @service.metadata_image.url %>">

Now we can say:

<meta name="twitter:image:src" content="<%= cdn_image_url(@service.metadata_image) %>">

And presto! We’re using our CDN to serve our attachments!

One last comment before ending this article, however.

If you paid attention to what I listed as being the benefits we wanted, you’ll see I said:

  1. Their links would always be publicly accessible

But if you look at the code we’re actually setting an expiration field. So what gives?

The thing is that I’m setting the link expiration to be the default expiration ActiveStorage gives to urls. Which is nil. So we’re all good. I kept that line there for 2 reasons. The first so you, the reader, could be made aware of this option and how to possibly set it. And 2 because if at some point we do desire these links to expire, we can configure ActiveStorage for that. Or just set some hard coded value. Whatever might work for us at that point.

Conclusion

As you can see, it was all pretty simple. What’s best is that this works with any storage provider you might want to use. As long as ActiveStorage is configured correctly, just make sure to generate your URL helpers with your CDN host.

It’s worth noting that there’s much more to the performance of an app than using CDNs. It just so happens that we here at FastRuby.io are experts in these issues. Make sure to check out our Tune Report opens a new window service page for more information on how we can make your applications respond quicker than two shakes of a lamb’s tail.

Ready to take your Rails application’s performance to the next level? Send us a message! opens a new window

Get the book