/**
* Common Data Sources
***/
data "aws_region" "current" {}
/**
* Server Function
***/
resource "aws_lambda_function" "server" {
description = "Server function for the website"
filename = var.server_function_config.filename
function_name = var.server_function_config.function_name
handler = var.server_function_config.handler
memory_size = var.server_function_config.memory_size
role = aws_iam_role.server.arn
runtime = var.server_function_config.runtime
source_code_hash = filebase64sha256(var.server_function_config.filename)
timeout = var.server_function_config.timeout
dynamic "tracing_config" {
for_each = var.server_function_config.tracing_enabled ? [1] : []
content {
mode = "Active"
}
}
dynamic "vpc_config" {
for_each = var.vpc_config.enabled ? [1] : []
content {
subnet_ids = var.vpc_config.subnet_ids
security_group_ids = var.vpc_config.security_group_ids
}
}
environment {
variables = {
CACHE_BUCKET_NAME = aws_s3_bucket.cache_bucket.id
CACHE_BUCKET_REGION = data.aws_region.current.name
CACHE_DYNAMO_TABLE = "INSERT_CACHE_DYNAMO_TABLE_HERE"
REVALIDATION_QUEUE_URL = aws_sqs_queue.revalidation_queue.id
REVALIDATION_QUEUE_REGION = data.aws_region.current.name
}
}
tags = merge(var.shared_tags, {
Name = var.server_function_config.function_name
})
}
resource "aws_iam_role" "server_lambda" {
name = var.server_function_config.function_name
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "server_lambda" {
name = var.server_function_config.function_name
policy = jsondecode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "arn:aws:logs:*:*:*"
},
{
Action = [
"s3:GetObject",
"s3:PutObject"
]
Effect = "Allow"
Resource = "${aws_s3_bucket.cache_bucket.arn}/*"
},
{
Action = [
"sqs:SendMessage"
]
Effect = "Allow"
Resource = aws_sqs_queue.revalidation_queue.arn
},
{
Action = [
"dynamodb:PutItem",
"dynamodb:GetItem"
]
Effect = "Allow"
Resource = "INSERT CACHE DYNAMO TABLE ARNS"
}
]
})
role = aws_iam_role.server_lambda.id
}
/**
* Image Optimization Function
***/
resource "aws_lambda_function" "image_optimization" {
architectures = ["arm64"]
description = "Image Optimization function for the website"
filename = var.image_optimization_function_config.filename
function_name = var.image_optimization_function_config.function_name
handler = var.image_optimization_function_config.handler
memory_size = var.image_optimization_function_config.memory_size
role = aws_iam_role.image_optimization.arn
runtime = var.image_optimization_function_config.runtime
source_code_hash = filebase64sha256(var.image_optimization_function_config.filename)
timeout = var.image_optimization_function_config.timeout
dynamic "tracing_config" {
for_each = var.image_optimization_function_config.tracing_enabled ? [1] : []
content {
mode = "Active"
}
}
dynamic "vpc_config" {
for_each = var.vpc_config.enabled ? [1] : []
content {
subnet_ids = var.vpc_config.subnet_ids
security_group_ids = var.vpc_config.security_group_ids
}
}
environment {
variables = {
BUCKET_NAME = aws_s3_bucket.asset_files_bucket.id
}
}
tags = merge(var.shared_tags, {
Name = var.image_optimization_function_config.function_name
})
}
resource "aws_iam_role" "image_optimization" {
name = var.image_optimization_function_config.function_name
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "image_optimization" {
name = var.image_optimization_function_config.function_name
policy = jsondecode({
Version = "2012-10-17"
Statement = [
{
Action = [
"s3:GetObject"
]
Effect = "Allow"
Resource = "${aws_s3_bucket.asset_files_bucket.arn}/*"
}
]
})
role = aws_iam_role.image_optimization.id
}
/**
* Asset Files Bucket
***/
resource "aws_s3_bucket" "asset_bucket" {
bucket = var.asset_bucket_config.bucket_name
tags = merge(var.shared_tags, {
Name = var.asset_bucket_config.bucket_name
})
}
/**
* Revalidation Function
***/
resource "aws_lambda_function" "revalidation_function" {
description = "Revalidation function for the website"
filename = var.revalidation_function_config.filename
function_name = var.revalidation_function_config.function_name
handler = var.revalidation_function_config.handler
memory_size = var.revalidation_function_config.memory_size
role = aws_iam_role.revalidation_function.arn
runtime = var.revalidation_function_config.runtime
source_code_hash = filebase64sha256(var.revalidation_function_config.filename)
timeout = var.revalidation_function_config.timeout
dynamic "tracing_config" {
for_each = var.revalidation_function_config.tracing_enabled ? [1] : []
content {
mode = "Active"
}
}
dynamic "vpc_config" {
for_each = var.vpc_config.enabled ? [1] : []
content {
subnet_ids = var.vpc_config.subnet_ids
security_group_ids = var.vpc_config.security_group_ids
}
}
tags = merge(var.shared_tags, {
Name = var.revalidation_function_config.function_name
})
}
resource "aws_iam_role" "revalidation_function" {
name = var.revalidation_function_config.function_name
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "revalidation_function" {
name = var.revalidation_function_config.function_name
role = aws_iam_role.revalidation_function.id
policy = jsondecode({
Version = "2012-10-17"
Statement = [
{
Action = [
"sqs:ReceiveMessage",
"sqs:DeleteMessage"
],
Effect = "Allow",
Resource = aws_sqs_queue.revalidation_queue.arn
}
]
})
}
resource "aws_lambda_event_source_mapping" "revalidation_queue" {
event_source_arn = aws_sqs_queue.revalidation_queue.arn
function_name = aws_lambda_function.revalidation_function.function_name
}
/**
* Revalidation Queue
***/
resource "aws_sqs_queue" "revalidation_queue" {
name = "revalidation-queue.fifo"
fifo_queue = true
tags = merge(var.shared_tags, {
Name = "revalidation-queue.fifo"
})
}
/**
* Revalidation Tag-to-path Mapping DynamoDB Table
***/
/**
* Cache Files Bucket
***/
resource "aws_s3_bucket" "cache_bucket" {
bucket = var.cache_bucket_config.bucket_name
tags = merge(var.shared_tags, {
Name = var.cache_bucket_config.bucket_name
})
}
/**
* Warmer Function
***/
resource "aws_lambda_function" "warmer" {
description = "Warmer function for the website"
filename = var.warmer_function_config.filename
function_name = var.warmer_function_config.function_name
handler = var.warmer_function_config.handler
memory_size = var.warmer_function_config.memory_size
role = aws_iam_role.warmer.arn
runtime = var.warmer_function_config.runtime
source_code_hash = filebase64sha256(var.warmer_function_config.filename)
timeout = var.warmer_function_config.timeout
dynamic "tracing_config" {
for_each = var.warmer_function_config.tracing_enabled ? [1] : []
content {
mode = "Active"
}
}
dynamic "vpc_config" {
for_each = var.vpc_config.enabled ? [1] : []
content {
subnet_ids = var.vpc_config.subnet_ids
security_group_ids = var.vpc_config.security_group_ids
}
}
environment {
variables = {
FUNCTION_NAME = var.server_function_config.function_name
CONCURRENCY = var.warmer_function_config.concurrency
}
}
tags = merge(var.shared_tags, {
Name = var.warmer_function_config.function_name
})
}
resource "aws_iam_role" "warmer_lambda" {
name = var.warmer_function_config.function_name
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "warmer_lambda" {
name = var.warmer_function_config.function_name
policy = jsondecode({
Version = "2012-10-17"
Statement = [
{
Action = [
"lambda:InvokeFunction"
]
Effect = "Allow"
Resource = aws_lambda_function.server.arn
}
]
})
role = aws_iam_role.warmer_lambda.id
}
/**
* Warmer Eventbridge Cron
***/
/**
* Server Function
***/
variable "server_function_config" {
description = "Configuration for the server function"
type = object({
filename = string
function_name = optional(string, "server")
handler = optional(string, "index.mjs")
memory_size = optional(number, 2048)
runtime = optional(string, "nodejs20.x")
timeout = optional(number, 10)
tracing_enabled = optional(bool, true)
})
}
/**
* Image Optimization Function
***/
variable "image_optimization_function_config" {
description = "Configuration for the image optimization function"
type = object({
filename = string
function_name = optional(string, "image-optimization")
handler = optional(string, "index.mjs")
memory_size = optional(number, 2048)
runtime = optional(string, "nodejs20.x")
timeout = optional(number, 10)
tracing_enabled = optional(bool, true)
})
}
/**
* Asset Files Bucket
***/
variable "asset_bucket_config" {
description = "Configuration for the asset files bucket"
type = object({
bucket_name = optional(string, "asset-files")
})
default = {
bucket_name = "asset-files"
}
}
/**
* Revalidation Function
***/
variable "revlidation_function_config" {
description = "Configuration for the revalidation function"
type = object({
filename = string
function_name = optional(string, "revalidation")
handler = optional(string, "index.mjs")
memory_size = optional(number, 2048)
runtime = optional(string, "nodejs20.x")
timeout = optional(number, 10)
tracing_enabled = optional(bool, true)
})
}
/**
* Cache Files Bucket
***/
variable "cache_bucket_config" {
description = "Configuration for the cache files bucket"
type = object({
bucket_name = optional(string, "cache-files")
})
default = {
bucket_name = "cache-files"
}
}
/**
* Warmer Function
***/
variable "warmer_function_config" {
description = "Configuration for the warmer function"
type = object({
filename = string
function_name = optional(string, "warmer")
handler = optional(string, "index.mjs")
memory_size = optional(number, 2048)
runtime = optional(string, "nodejs20.x")
timeout = optional(number, 10)
tracing_enabled = optional(bool, true)
concurrency = optional(number, 1)
})
}
/**
* Networking
***/
variable "vpc_config" {
description = "Configuration for the AWS networking"
type = object({
enabled = optional(bool, false)
subnet_ids = optional(list(string), [])
security_group_ids = optional(list(string), [])
})
default = {
enabled = false
subnet_ids = []
security_group_ids = []
}
}
/**
* Tags
***/
variable "shared_tags" {
description = "Tags to be applied to all resources"
type = map(string)
default = {}
}
/**
* Server Function
***/
output "server_function" {
description = "The server function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation."
value = aws_lambda_function.server_function
}
/**
* Image Optimization Function
***/
output "image_optimization_function" {
description = "The image optimization function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation."
value = aws_lambda_function.image_optimization
}
/**
* Asset Files Bucket
***/
output "asset_bucket" {
description = "The asset files bucket attributes. Attributes are documented under the aws_s3_bucket resource in the AWS Provider documentation."
value = aws_s3_bucket.asset_bucket
}
/**
* Revalidation Function
***/
output "revalidation_function" {
description = "The revalidation function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation."
value = aws_lambda_function.revalidation
}
/**
* Cache Files Bucket
***/
output "cache_bucket" {
description = "The cache files bucket attributes. Attributes are documented under the aws_s3_bucket resource in the AWS Provider documentation."
value = aws_s3_bucket.cache_bucket
}
/**
* Warmer Function
***/
output "warmer_function" {
description = "The warmer function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation."
value = aws_lambda_function.warmer
}
/**
* Revalidation Queue
***/
output "revalidation_queue" {
description = "The revalidation queue attributes. Attributes are documented under the aws_sqs_queue resource in the AWS Provider documentation."
value = aws_sqs_queue.revalidation_queue
}
/**
* Terraform and Provider versions
***/
terraform {
required_version = ">= 0.13"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
<!-- BEGIN_TF_DOCS -->
## Requirements
| Name | Version |
| ------------------------------------------------------------------------ | ------- |
| <a name="requirement_terraform"></a> [terraform](#requirement_terraform) | >= 0.13 |
| <a name="requirement_aws"></a> [aws](#requirement_aws) | ~> 3.0 |
## Providers
| Name | Version |
| ------------------------------------------------ | ------- |
| <a name="provider_aws"></a> [aws](#provider_aws) | ~> 3.0 |
## Modules
No modules.
## Resources
| Name | Type |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| [aws_iam_role.image_optimization](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.revalidation_function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.server_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.warmer_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.image_optimization](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.revalidation_function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.server_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.warmer_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_lambda_event_source_mapping.revalidation_queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource |
| [aws_lambda_function.image_optimization](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_lambda_function.revalidation_function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_lambda_function.server](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_lambda_function.warmer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_s3_bucket.asset_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket.cache_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_sqs_queue.revalidation_queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
## Inputs
| Name | Description | Type | Default | Required |
| --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | :------: |
| <a name="input_asset_bucket_config"></a> [asset_bucket_config](#input_asset_bucket_config) | Configuration for the asset files bucket | <pre>object({<br> bucket_name = optional(string, "asset-files")<br> })</pre> | <pre>{<br> "bucket_name": "asset-files"<br>}</pre> | no |
| <a name="input_cache_bucket_config"></a> [cache_bucket_config](#input_cache_bucket_config) | Configuration for the cache files bucket | <pre>object({<br> bucket_name = optional(string, "cache-files")<br> })</pre> | <pre>{<br> "bucket_name": "cache-files"<br>}</pre> | no |
| <a name="input_image_optimization_function_config"></a> [image_optimization_function_config](#input_image_optimization_function_config) | Configuration for the image optimization function | <pre>object({<br> filename = string<br> function_name = optional(string, "image-optimization")<br> handler = optional(string, "index.mjs")<br> memory_size = optional(number, 2048)<br> runtime = optional(string, "nodejs20.x")<br> timeout = optional(number, 10)<br> tracing_enabled = optional(bool, true)<br> })</pre> | n/a | yes |
| <a name="input_revlidation_function_config"></a> [revlidation_function_config](#input_revlidation_function_config) | Configuration for the revalidation function | <pre>object({<br> filename = string<br> function_name = optional(string, "revalidation")<br> handler = optional(string, "index.mjs")<br> memory_size = optional(number, 2048)<br> runtime = optional(string, "nodejs20.x")<br> timeout = optional(number, 10)<br> tracing_enabled = optional(bool, true)<br> })</pre> | n/a | yes |
| <a name="input_server_function_config"></a> [server_function_config](#input_server_function_config) | Configuration for the server function | <pre>object({<br> filename = string<br> function_name = optional(string, "server")<br> handler = optional(string, "index.mjs")<br> memory_size = optional(number, 2048)<br> runtime = optional(string, "nodejs20.x")<br> timeout = optional(number, 10)<br> tracing_enabled = optional(bool, true)<br> })</pre> | n/a | yes |
| <a name="input_shared_tags"></a> [shared_tags](#input_shared_tags) | Tags to be applied to all resources | `map(string)` | `{}` | no |
| <a name="input_vpc_config"></a> [vpc_config](#input_vpc_config) | Configuration for the AWS networking | <pre>object({<br> enabled = optional(bool, false)<br> subnet_ids = optional(list(string), [])<br> security_group_ids = optional(list(string), [])<br> })</pre> | <pre>{<br> "enabled": false,<br> "security_group_ids": [],<br> "subnet_ids": []<br>}</pre> | no |
| <a name="input_warmer_function_config"></a> [warmer_function_config](#input_warmer_function_config) | Configuration for the warmer function | <pre>object({<br> filename = string<br> function_name = optional(string, "warmer")<br> handler = optional(string, "index.mjs")<br> memory_size = optional(number, 2048)<br> runtime = optional(string, "nodejs20.x")<br> timeout = optional(number, 10)<br> tracing_enabled = optional(bool, true)<br> concurrency = optional(number, 1)<br> })</pre> | n/a | yes |
## Outputs
| Name | Description |
| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| <a name="output_cache_bucket"></a> [cache_bucket](#output_cache_bucket) | The cache files bucket attributes. Attributes are documented under the aws_s3_bucket resource in the AWS Provider documentation. |
| <a name="output_image_optimization_function"></a> [image_optimization_function](#output_image_optimization_function) | The image optimization function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation. |
| <a name="output_revalidation_function"></a> [revalidation_function](#output_revalidation_function) | The revalidation function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation. |
| <a name="output_revalidation_queue"></a> [revalidation_queue](#output_revalidation_queue) | The revalidation queue attributes. Attributes are documented under the aws_sqs_queue resource in the AWS Provider documentation. |
| <a name="output_server_function"></a> [server_function](#output_server_function) | The server function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation. |
| <a name="output_warmer_function"></a> [warmer_function](#output_warmer_function) | The warmer function attributes. Attributes are documented under the aws_lambda_function resource in the AWS Provider documentation. |
<!-- END_TF_DOCS -->
About
This is an opinated way to launch a Next.js based website on AWS. This architecture is based on utilizing OpenNext for generation of the site bundles. Refer to OpenNext for more information on what Next.js features it supports.Installation
1
Copy and paste all code files above into a folder.
Copy the main.tf, variables.tf, outputs.tf, versions.tf, and README.md into
a folder (ex. website).
2
Reference the new module as a local or remote module.
module "website" {
source = "path-to-copied-website-module"
server_function_config = {
filename = "server.zip"
}
image_optimization_function_config = {
filename = "image-optimization.zip"
}
revlidation_function_config = {
filename = "revlidation.zip"
}
warmer_function_config = {
filename = "warmer.zip"
}
}