Create your own serverless image optimizer for Next.js in 5 minutes
Tags: nextjs, terraform, devops, aws
Published: 2021-05-01 15:48:34

With the introduction of Next.js 10 the new [next/image component](https://nextjs.org/docs/api-reference/next/image) was added. It is a modern approach to enhance the `<img>` HTML-Element by adjusting the image based on the viewer’s needs. By recognizing what image extensions (e.g. newer formats like [WebP](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types)) the client browser supports, it converts the source accordingly and also resizes the image optimized for the screen width of the viewer.

The optimization itself is performed on the fly when the resource is requested by the user. This makes the solution perfectly scalable whether it is used for 10 or 10 million images.

But what if you self-host your app as a single server or inside a docker container? Spinning up more instances just to deal with the increased traffic caused by image processing can be pretty expensive.

Fortunately the Next.js developers have a solution for this: You can use a cloud-based [external image loader](https://nextjs.org/docs/basic-features/image-optimization#loader) to perform this task. Currently Imgix, Cloudinary and Akamai are officially supported.

Since we were already using Amazon Web Services (AWS) we thought about how we could bring this service directly to their platform without relying on third-party cloud services. The basic idea was to isolate the part from Next.js core responsible for the image optimization, pack it into a serverless function (AWS Lambda) and use it as an external loader.

![Architecture diagram of the Terraform module](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21xey6ao2by6dloez5ut.png)
<figcaption>
Serverless AWS stack for image optimization with Next.js
</figcaption>
<br />
  
This way we would be able to achieve full support of all `next/image` features and a maximum of scalability because AWS Lambda can scale up and down dynamically to met the demand.
Since we do not want to run the serverless function on every request we put it behind a CloudFront distribution that is able to serve recurring requests for the same resources from cache. It also makes the whole thing noticeably faster since CloudFront CDN network is able to store the cache close the viewers location.

Because we are already manage most of our DevOps stack with [Terraform](https://www.terraform.io/), we also used it to create the image optimizer. This also makes it possible to share the whole module so that it can easily be reused.

## Deploy the serverless image optimizer to AWS

All you need to create your own image optimizer in 5 minutes is an AWS account and Terraform installed.

Then create a new file called `main.tf` (Can be added to the same directory as your Next.js app), which contains the definition and configuration of the module:

```tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Main AWS region where the resources should be created in
# Should be close to where your Next.js deployment is located
provider "aws" {
  region = "us-east-1"
}

module "next_image_optimizer" {
  source = "milliHQ/next-js-image-optimization/aws"

  next_image_domains = ["example.com", "sub.example.com"]
}

output "domain" {
  value = module.next_image_optimizer.cloudfront_domain_name
}
```

The `next_image_domains` variable should contain the domains where the external images should be fetched from. It takes the same configuration as the [Domains setting](https://nextjs.org/docs/basic-features/image-optimization#domains) from Next.js.

After that you are all set to deploy the module to your AWS account. All you need to do is running the two Terraform commands:

```sh
terraform init
terraform apply
```

After Terraform has finished deploying the module, it shows you the following output on the Terminal:

```sh
Apply complete!

Outputs:

domain = "<distribution-id>.cloudfront.net"
```

The domain of the CloudFront distribution (`"<distribution-id.cloudfront.net"`) is all you need to tell your Next.js app to use the newly created image optimizer. Open or create your `next.config.js` and add the following lines:

```diff
module.exports = {
+  images: {
+    path: 'https://<distribution-id>.cloudfront.net/_next/image'
+  },
}
```

Last step is now to redeploy your Next.js app with the changed configuration. Now you are using your own, self-hosted image optimizer.

---

You can take look at the source code of the module on GitHub, since everything is Open Source:

{% github milliHQ/terraform-aws-next-js-image-optimization %}

---

## Looking for a full stack solution to deploy Next.js on AWS?

If you don't want to mess around with configuration and want a complete solution to host Next.js serverless on AWS, please checkout our [Terraform Next.js module for AWS](https://github.com/milliHQ/terraform-aws-next-js). It has image optimization already built in and can handle most features from Next.js in a cost-efficient way.