Deploy Serverless Next.js to AWS with Terraform 1.1
Terraform for better or worse is frequently updated with new versions. Many of these introduce incompatibilities with previous versions that require manual rework of Terraform definitions as well as require updating the local or remote Terraform state. I originally developed a solution for deploying Next.js to AWS using Terraform version 0.12
and that has been working well for over a year now. However, recently AWS announced that changes to their API would require an update of the AWS Terraform Provider. While there is an option to patch an older version of the Terraform provider, I figured that may be as good an excuse as any to update the Goldstack Next.js + Bootstrap Template to the latest version of Terraform, which is 1.1
as of this writing.
Next.js is optimised for deployment to the Vercel platform - which provides a fast and easy way to deploy Next.js projects. Apart from that, Next.js can also be deployed using Docker or a virtual machine that can run Node.js. My aim is to deploy Next.js to AWS using Serverless solutions that are low-cost and easy to scale. Thus, I won't be setting up any EC2 instances or EKS/ECS containers. Instead, I will use the following components:
- Amazon CloudFront: As a low-cost, high performance CDN
- Amazon Route 53: For configuration of DNS records
- Amazon S3: For storage of compiled deployment artifacts
- AWS Lambda: For supporting dynamic routing
- AWS Certificate Manager: For generating SSL certificates
This post provides an overview of the Terraform resources required to configure the infrastructure for hosting a Next.js application on AWS.
tldr;
If you simply want to get started developing your own Next.js project to be deployed on Terraform:
- Use the Goldstack template to create a customised template for your needs
- Check out the Example project on GitHub
Terraform Resources
In order to define the above infrastructure in Terraform, we need the following Terraform resources:
For defining our certificate and setting up the domain name:
- aws_acm_certificate: To define the certificate we use to allow secure connections via
https://
to our website. AWS provides these certificates for free and will renew them automatically as well. - aws_route53_record: To define the DNS records for our website. We also use this resource to help with validating the TLS certificate.
- aws_acm_certificate_validation: To assist with the validation of the TLS certificate
See the configuration for each resource in main.tf.
For configuring our CDN:
- aws_s3_bucket: For storing the JavaScript/HTML/etc. files Next.js generates.
- aws_cloudfront_distribution: For creating a new CloudFront Distribution.
See the configuration for each resource in root.tf. We are also configuring a second CloudFront distribution to be used for the forwarding domain (e.g. www.mydomain.com
to mydomain.com
). This distribution is defined in redirect.tf.
For supporting dynamic Next.js routing:
- aws_lambda_function: To define a lambda function that will help with Next.js dynamic routes. Find the source code for this function in lambda.ts. This is an edge lambda that will be used by CloudFront.
Find the definition of this resource and other resources required for running the lambda in edge.tf.
Changes from Terraform 0.12 to 1.1
Terraform introduced a number of changes, especially in the version 0.13
and 0.14
. For the resources required for this project they were:
- The way provides are defined has changed in
0.13
. They are now defined as:
terraform {
required_providers {
archive = {
source = "hashicorp/archive"
version = "2.2.0"
}
aws = {
source = "hashicorp/aws"
version = "3.72.0"
}
}
required_version = ">= 0.13"
}
- Version
0.14
also introduced a lockfile to keep track of versions of modules used and their transitive dependencies. See .terraform.lock.hcl for the lockfile generated for this project. - There were a number of changes to aws_acm_certificate resource in newer versions of the Terraform AWS Provider. Previously the Route 53 resource could be defined as follows:
resource "aws_route53_record" "wildcard_validation" {
name = aws_acm_certificate.wildcard_website.domain_validation_options[0].resource_record_name
type = aws_acm_certificate.wildcard_website.domain_validation_options[0].resource_record_type
zone_id = data.aws_route53_zone.main.zone_id
records = [aws_acm_certificate.wildcard_website.domain_validation_options[0].resource_record_value]
ttl = "60"
}
But with the most recent AWS Terraform provider, it needs to be defined as (see Terraform and AWS Wildcard Certificates Validation) :
resource "aws_route53_record" "wildcard_validation" {
provider = aws.us-east-1
for_each = {
for dvo in aws_acm_certificate.wildcard_website.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
# Skips the domain if it doesn't contain a wildcard
if length(regexall("\\*\\..+", dvo.domain_name)) > 0
}
allow_overwrite = true
name = each.value.name
type = each.value.type
zone_id = data.aws_route53_zone.main.zone_id
records = [each.value.record]
ttl = 60
}
Getting Next.js Ready for Deployment to AWS
In order to deploy Next.js to AWS, we need to use the Static HTML Export feature of Next.js. We run next export
(see package.json) and upload the directory generated by the export to S3 using the Goldstack Next.js template utilities - this will also take care of packing up the dynamic routing config and deploying the edge lambda used by CloudFront routing.
Relying on the static HTML export of Next.js comes with several trade-offs:
- API Routes are not supported. Goldstack provides an Serverless API template and a template for a serverless Express API that can be used to set up an API in the same project.
- Next.js
<Image>
component is not supported (see Next.js Image loader). Useimg
instead. The sample project is already set up to use next-optimized-images which can instead be used to serve images. - Manual configuration required to server files in the
public/
image directly, for details see 404 Not found for files in public folder
There are many different ways to deploy Next.js to AWS, for instance using AWS Amplify or using EC2. The solution pursued in the example project aims to provide a lightweight, yet flexible solution that makes full use of the capabilities of Terraform while creating low-cost, scalable infrastructure. If you have any questions or ideas how to improve this approach, please be welcome to head over to the Goldstack GitHub project and raise an issue.
Cover image by kreatikar