Brendan McKenzie

Serverless CDN

Thursday, 12 September 2019

tl;dr: See this repository for the serverless setup - https://github.com/brendanmckenzie/serverless-cdn

In optimising my website for the modern web, using React and Next.JS I had trouble finding a nice solution for hosting media (primarily images) served through my website.

Keeping in mind performance, I was looking for a nice method of serving images in a multitude of dimensions - linking this in with the responsive srcset option of the <img /> and <picture /> tags.

There are a number of paid solutions out there, most are quite cost effective are probably the kind of solution you would go for, however, another goal of this process of modernising my website was to try to see how cheap I could make it. The paid solutions I looked into were imgix.com and cloudinary.com - both strong products that I could recommend.

now.sh build host my Next.JS website at no cost, so why pay more for my CDN.

I already have an internal system that stores my media in an S3 bucket, so it made sense that I could expand that to suit my need.

The main problem I needed to solve was how to dynamically resize images stored in my S3 bucket. I came across an article on the amazon.com website that did basically what I wanted, it was slightly dated and cumbersome. I wanted to take what they had built, adapt it to my setup and optimise the development flow.

The solution employed two Lambda@Edge functions, a Cloudfront distribution and an S3 bucket.

The process is fairly simple, there is a “viewer request” and an “origin response” Lambda function associated with my Cloudfront distribution.

The viewer request function checks the inbound Cloudfront request, determines if the request is actually requesting processing of assets in the S3 bucket, modifies the requested URL, then passes the request on to be processed by the origin response function. If a request is made for asset abc123 to be resized to a height and width of 200

The origin response function is actually unaware of the viewer request function, the inbound requested URL would look like /abc123?w=500, where after processing by the viewer request function it would look like /p/abc123_w,500. Given this, the origin response method reacts when a request comes for /p/abc123_w,500 and is currently non-existent. When a 404 is returned from the origin (the S3 bucket), the origin response function generates the resized asset, stores it in the S3 bucket at the correlating path, and then returns it to the requester. Any subsequent request for the asset resized to the same dimensions will read from S3 bucket.

My first step was to integrate the code with the serverless.com framework. This would make deployments and updates much easier to manage. You can see the final product, with serverless.yml, here.

After I had my serverless.com setup working I wanted to modernise the code - the examples were written targetting nodejs 6.x which is no longer supported by Lambda.

Once deployed, access resources in my S3 bucket was as simple as passing query string parameters to my Cloudfront requests.

Side note - while working on this I came across dashbird.io which makes monitoring Lambda functions a much simpler process than CLI tools and jumping around Cloudwatch logs.