image optimization
Customer managed solution based on CloudFront, S3, and Lambda
The most common use case for image optimization is automatically formatting based on the user’s browser capabilities and allowing the front-end to resize the image. Popular web development frameworks such as Next.JS provide responsive image components that automatically select image sizes based on the device viewport. The following diagram illustrates the recommended architecture for this common use case:
- The user sends a HTTP request for an image with specific transformations, such as encoding and size. The transformations are encoded in the URL, more precisely as query parameters. An example URL would look like this:
https://exmaples.com/images/cats/mycat.jpg?format=webp&width=200
. - The request is processed by a nearby CloudFront edge location providing the best performance. Before passing the request upstream, a CloudFront Function is executed on viewer request event to rewrite the request URL. CloudFront Functions is a feature of CloudFront that allows you to write lightweight functions in JavaScript for high-scale, latency-sensitive CDN customizations. In our architecture, we rewrite the URL to validate the requested transformations and normalize the URL by ordering transformations and convert them to lower case to increase the cache hit ratio. When an automatic transformation is requested, the function also decides about the best one to apply. For example, if the user asks for the most optimized image format (JPEG,WebP, or AVIF) using the directive
format=auto
, CloudFront Function will select the best format based on the Accept header present in the request. - If the requested image is already cached in CloudFront then there will be a cache hit and the image is returned from CloudFront cache. To increase the cache hit ratio, we enable Origin shield, a feature of CloudFront that acts as an additional layer of caching before the origin, to further offload it from requests. If the Image is not in CloudFront cache, then the request will be forwarded to an S3 bucket, which is created to store the transformed images. If the requested image is already transformed and stored in S3, then it is simply served and cached in CloudFront.
- Otherwise, S3 will respond with a 403 error code, which is detected by CloudFront’s Origin Failover. Thanks to this native feature, CloudFront retries the same URL but this time using the secondary origin based on Lambda function URL. When invoked, the Lambda function downloads the original image from another S3 bucket, where original images are stored, transforms it using Sharp library, stores the transformed image in S3, then serve it through CloudFront where it will be cached for future requests.
To deploy this solution, follow the steps in this blog. In addition, the blog shows you how to use it in the image component of Next.JS. Note that this solution allows you to disable the ability to store the transformed images in S3 and rely solely on the CloudFront cache to serve the transformed images.
Deploy solution using CDK
Preparation:
# Install node
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
$ nvm install 20
# Install AWS CDKCLI
$ npm install -g aws-cdk
$ cdk --version
Start deployment:
git clone https://github.com/aws-samples/image-optimization.git
cd image-optimization
npm install
cdk bootstrap
npm run build
cdk deploy -c S3_IMAGE_BUCKET_NAME=’YOUR_S3_BUCKET_NAME’
Clean up: To delete the cloud resources created for this solution, simply execute the following command:
cdk destroy
Comments