If your users are located all around the world or, at least, far from where your application servers are, serving static assets through a CDN is a good idea.
User experience improves because your page loads faster by saving the network latency produced by the distance to the server.
And, by decantation, SEO improves as well. Serving your assets from a CDN improves Core Web Vitals which are a set of proxy metrics for user experience that Google uses to rank sites.
Now that you know what the benefits are, let's get to work:
How does serving assets through a CDN work
By static assets we understand every asset in our application that's not very likely to change.
They're usually the ones managed by the asset pipeline: images like logos or icons, CSS, JavaScript, self-hosted fonts and even things like our site's favicon.
We already learned how to serve Active Storage uploads with a CDN, and static assets work very similarly.
The way it works to serve assets is basically the same as it works for file uploads.
The flows are explained in the following diagrams:
The above diagram shows the default Rails asset delivery: every static asset like CSS, JavaScript or self-hosted fonts are hosted on the server and delivered by it.
If the server is far from the user requesting the assets, network latency makes the process take longer thus degrading the user experience.
Conversely, delivering assets using a CDN looks like this:
Here, the CDN's job is to request assets from the origin (our server) and cache them if not already cached and return them to the user which is, at most, 100 ms away from the CDN PoP: point of presence, the actual CDN server that delivers the asset.
For this example, there's actually points of presence in Madrid, Barcelona, and Lisbon so our user is guaranteed to be close from the PoP.
Configuring CDN asset delivery with Rails is pretty straightforward, however we have to set our CloudFront distribution in order to make everything work:
Configuring a CloudFront distribution
In order to serve our assets from a CDN, we first need to configure a distribution in the CloudFront part of the AWS Console.
Let's go through the steps:
Create an AWS account
Before anything, we need to create an AWS account. We can do so by following this link.
You will need to provide a valid email and an account name. After validating your email address, you will be prompted to set a password and then provide some information about you, including billing information.
After completing the registration steps you have access to the whole suite of AWS products and a 12-month period free tier.
Create a CloudFront distribution with our domain as an origin
The second step is to create a distribution, which is the set of servers that will be serving our application's assets.
For this step we need to choose our application's domain as an Origin domain
we pick HTTPS only
for the protocol because we have the config.force_ssl
set to true. The HTTPS port is 443 which is the default value. We set TLSv1.2 as the minimum origin SSL protocol because our server is using TLSv1.3.
The next step is to give a name to the distribution, which is optional, and configure the headers that CloudFront will send to our application:
The headers we're configuring here are forwarded to the client in each request to CloudFront that results in a cache MISS:
- Access-Control-Allow-Origin: controls which domains can access the resources. In this case we only want the resources available from our application domain.
- Access-Control-Allow-Headers: specify which headers can be included in a request when it is sent to the sever.
Origin
is the domain that makes the request,Content-Type
indicates the media type of the request andAccept
informs our server which content types the client can handle. - Access-Control-Allow-Methods: specifies the HTTP methods that are allowed when accessing the resource.
GET
is used to retrieve the assets andOPTIONS
is used in preflight requests. - Access-Control-Allow-Credentials: it allows CloudFront to pass along any credentials that might be included in requests from the client to the server.
After this, we configure the “Default Cache Behavior” section:
We practically leave the default options except for the "Allowed HTTP methods" which we set to GET
and HEAD
.
We also configure CloudFront to compress objects which allows CF to compress certain files received from the origin before delivering them to the user.
There's no need for viewer restriction, at least for our actual use case.
Next up is the “Cache key and origin requests” section:
For this section, we pick the “Legacy Cache Settings” and the Response headers policy to CORS-with-preflight-SecurityHeadersPolicy
.
This policy sets the Access-Control-Allow-Origins
to *
which is very permissive. In case you need to customize that to only allow a limited set of origins you would have to create a cache and an origin request policy.
Lastly, and if you want, you can configure the Settings section:
We can leave all the default values. However, you can pick a limited set of edge locations in case your users are generally located in a limited area.
Rails application configuration
After we configured the CloudFront part of things, we just need to configure one config variable to make everything work in production.
# config/environments/production.rb
config.asset_host = 'd123456abcdefg.cloudfront.net' # The host for your newly create distribution
And that's it. This configuration would replace our local origin with the distribution we just created, and the process explained at the beginning of the article will take place for every request.
You could use Rails credentials or environment variables to handle this configuration, but you should know that the distribution will be displayed in the DOM and Network requests anyway.
Using a custom subdomain to serve assets
Another feature that's desirable when serving assets with a CDN is using a custom subdomain like cdn.origindomain.com
or assets.origindomain.com
.
There are a couple of reasons we could use a custom subdomain instead of the domain provided by CloudFront to serve assets:
- Consistency: some curious users might find out where the assets are loading from and be curious about the “weird” URL or domain they come from.
- Versatility: we could change providers without causing breaking changes.
- SEO Benefits: assets like images or videos might be given more weight if they come from our domain. Also, if somebody links to our assets from their domain, we get to keep the authority.
- Vanity and trust: if we need to share an asset like a PDF with someone, the fact that it is hosted on our domain is more trustworthy. Especially for non-technical users.
Let's follow the steps to make this work:
Set an SSL certificate for your subdomain
In order to use a subdomain to deliver assets with CloudFront, or any CDN for that matter, we need to set an SSL certificate for the subdomain.
To keep it all under the AWS umbrella, we'll be requesting the certificate using AWS Certificate Manager..
The first step would be to go back to your AWS console and search for the certificate manager and access it. Once you're in there you should see something like this:
We proceed to click on the "Request a Certificate" button. After that we should see the "Request certificate" step:
We pick the only available option which is requesting a public certificate.
For the following step, we need to pick a fully qualified domain name, which in our case would be something like cdn.origindomain.com
. For the validation method we want to pick DNS validation because it's quicker and more straightforward. For the encryption algorithm we pick RSA 2048
which is the default value.
Then we click on the Request button at the bottom right part of the screen, and we should be redirected to the view for our specific certificate. At the middle of the screen we should see something like this:
Here we can find the CNAME name
and value
, we will use those values to set up the CNAME registry in our domain registrar.
CNAME config with our domain registrar
In order for Amazon to verify our domain ownership we need to create a CNAME entry in our domain registrar.
I use Hover, so the CNAME config looks like this:
We then paste the name
value provided by AWS in our HOSTNAME
field and the value
in our TARGET NAME
field.
After that, we click on the Save Changes button, and then we wait until the DNS changes are propagated.
Depending on your registrar, after maybe 10–15 minutes (it can vary) when you go to the Certificates section of the AWS Certificate Management console, you should see that the status for your certificate changed from Pending validation
to Issued
.
After your certificate is successfully issued by AWS, we now need to add a custom domain for the CloudFront distribution we created before.
To do that, we need to go to our distribution page in the AWS console and click on the Edit button for the Settings section that's located at the middle of the screen when seeing the distribution page.
Then, on the settings page, we should add our custom subdomain and the certificate we just registered with ACM:
The last step is to add an extra CNAME entry to associate our subdomain with the CloudFront distribution. We have to add cdn
to our HOSTNAME
and the distribution name to the TARGET NAME
:
Now, we have to wait until the domain propagates in order to serve our static assets with a CDN and from a custom subdomain.
I hope you've enjoyed the tutorial and that you can make it work for your next awesome application!