Production-ready Terraform module for deploying Next.js applications to AWS using the OpenNext architecture. This module creates a serverless infrastructure with CloudFront, Lambda, S3, and other AWS services optimized for Next.js applications.
Created and maintained by Stakpak Agent - A terminal-native DevOps Agent built for operations & infrastructure automation.
- ✅ Flexible DNS Configuration: Support for Route53, external DNS providers (Cloudflare, etc.), or CloudFront-only deployments
- ✅ Certificate Management: Create new ACM certificates or bring your own
- ✅ Serverless Architecture: Lambda functions for SSR, API routes, and image optimization
- ✅ Global CDN: CloudFront distribution with optimized caching strategies
- ✅ ISR Support: Full support for Incremental Static Regeneration with DynamoDB and SQS
- ✅ Cold Start Mitigation: Optional Lambda warmer to keep functions warm
- ✅ Cost Optimized: ARM64 Lambda support, intelligent caching, and pay-per-use pricing
- ✅ Security Best Practices: Private S3 buckets, CloudFront OAC, HTTPS enforcement
- ✅ Production Ready: Comprehensive monitoring, logging, and error handling
┌─────────────┐
│ Users │
└──────┬──────┘
│
▼
┌─────────────────────────────────────────┐
│ CloudFront CDN │
│ ┌──────────────────────────────────┐ │
│ │ Custom Domain (Optional) │ │
│ │ SSL/TLS Certificate │ │
│ └──────────────────────────────────┘ │
└────┬────────────┬──────────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────────┐
│ S3 │ │ Lambda │ │ Lambda │
│ Assets │ │ Server │ │ Image Opt │
└─────────┘ └────┬────┘ └──────────────┘
│
┌─────────┴─────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ S3 │ │DynamoDB │
│ Cache │ │ Table │
└─────────┘ └─────────┘
│
▼
┌─────────┐
│ SQS │
│ Queue │
└─────────┘
- AWS Account with appropriate permissions
- Terraform >= 1.5
- Next.js Application built with OpenNext
- Node.js >= 18.x (for building the app)
cd your-nextjs-app
npm run build
npx open-next@latest buildThis creates a .open-next directory with Lambda-ready bundles.
module "nextjs_app" {
source = "path/to/terraform-aws-nextjs-opennext"
app_name = "my-nextjs-app"
opennext_build_path = "../my-nextjs-app/.open-next"
# Lambda configuration
lambda_architecture = "arm64"
lambda_memory_size = 1024
}module "nextjs_app" {
source = "path/to/terraform-aws-nextjs-opennext"
providers = {
aws = aws
aws.us_east_1 = aws.us_east_1 # Required for ACM certificate
}
app_name = "my-nextjs-app"
domain_name = "app.example.com"
dns_provider = "route53"
route53_zone_id = "Z1234567890ABC"
opennext_build_path = "../my-nextjs-app/.open-next"
}module "nextjs_app" {
source = "path/to/terraform-aws-nextjs-opennext"
providers = {
aws = aws
aws.us_east_1 = aws.us_east_1
}
app_name = "my-nextjs-app"
domain_name = "app.example.com"
dns_provider = "external"
opennext_build_path = "../my-nextjs-app/.open-next"
}
# After apply, create DNS record in your provider:
# Type: CNAME or ALIAS
# Name: app.example.com
# Target: module.nextjs_app.cloudfront_domain_namemodule "nextjs_app" {
source = "path/to/terraform-aws-nextjs-opennext"
providers = {
aws = aws
aws.us_east_1 = aws.us_east_1
}
app_name = "my-nextjs-app"
domain_name = "app.example.com"
dns_provider = "external"
certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/abc-123"
create_certificate = false
opennext_build_path = "../my-nextjs-app/.open-next"
}terraform init
terraform plan
terraform apply# Get bucket name from output
ASSETS_BUCKET=$(terraform output -raw assets_bucket_name)
# Upload hashed static files (long cache)
aws s3 sync .open-next/assets s3://$ASSETS_BUCKET/ \
--cache-control "public,max-age=31536000,immutable" \
--exclude "*" \
--include "_next/static/*"
# Upload public assets (shorter cache)
aws s3 sync .open-next/assets s3://$ASSETS_BUCKET/ \
--cache-control "public,max-age=0,s-maxage=31536000,must-revalidate" \
--exclude "_next/*"
# Upload cache files
CACHE_BUCKET=$(terraform output -raw cache_bucket_name)
aws s3 sync .open-next/cache s3://$CACHE_BUCKET/terraform output website_url| Variable | Type | Description |
|---|---|---|
app_name |
string |
Application name (used for resource naming) |
opennext_build_path |
string |
Path to .open-next directory |
| Variable | Type | Default | Description |
|---|---|---|---|
domain_name |
string |
"" |
Custom domain name |
dns_provider |
string |
"none" |
DNS provider: route53, external, or none |
route53_zone_id |
string |
"" |
Route53 zone ID (required for route53 provider) |
certificate_arn |
string |
"" |
Existing ACM certificate ARN in us-east-1 |
create_certificate |
bool |
true |
Whether to create new certificate |
| Variable | Type | Default | Description |
|---|---|---|---|
lambda_architecture |
string |
"arm64" |
Lambda architecture: arm64 or x86_64 |
lambda_memory_size |
number |
1024 |
Server Lambda memory (MB) |
lambda_timeout |
number |
10 |
Server Lambda timeout (seconds) |
image_optimization_memory |
number |
1536 |
Image Lambda memory (MB) |
image_optimization_timeout |
number |
30 |
Image Lambda timeout (seconds) |
warmer_enabled |
bool |
true |
Enable Lambda warmer |
warmer_concurrency |
number |
1 |
Number of instances to keep warm |
warmer_schedule |
string |
"rate(5 minutes)" |
Warmer schedule expression |
| Variable | Type | Default | Description |
|---|---|---|---|
cloudfront_price_class |
string |
"PriceClass_100" |
Price class: PriceClass_All, PriceClass_200, PriceClass_100 |
cloudfront_geo_restriction_type |
string |
"none" |
Geo restriction: none, whitelist, blacklist |
cloudfront_geo_restriction_locations |
list(string) |
[] |
Country codes for geo restriction |
cloudfront_minimum_protocol_version |
string |
"TLSv1.2_2021" |
Minimum TLS version |
| Variable | Type | Default | Description |
|---|---|---|---|
assets_bucket_name |
string |
"" |
Custom assets bucket name (auto-generated if empty) |
cache_bucket_name |
string |
"" |
Custom cache bucket name (auto-generated if empty) |
enable_s3_versioning |
bool |
true |
Enable S3 versioning |
cache_expiration_days |
number |
30 |
Cache expiration in days |
cloudfront_distribution_id- Distribution IDcloudfront_distribution_arn- Distribution ARNcloudfront_domain_name- CloudFront domain (use if no custom domain)website_url- Full website URL
assets_bucket_name- Assets bucket namecache_bucket_name- Cache bucket name
server_function_name- Server Lambda nameserver_function_arn- Server Lambda ARNimage_optimization_function_name- Image optimization Lambda namerevalidation_function_name- Revalidation Lambda name
acm_certificate_arn- Certificate ARN (if created)acm_certificate_domain_validation_options- Validation options for external DNSroute53_record_fqdn- Route53 record FQDN (if managed)dns_configuration_required- DNS setup instructions for external providers
dns_provider = "route53"
route53_zone_id = "Z1234567890ABC"The module automatically creates DNS records and validates the certificate.
dns_provider = "external"
domain_name = "app.example.com"After terraform apply, the module outputs validation records. You need to:
-
For Certificate Validation (if creating new cert):
terraform output acm_certificate_domain_validation_options
Create the CNAME records in your DNS provider.
-
For Application Access:
terraform output dns_configuration_required
Create an ALIAS or CNAME record pointing to the CloudFront domain.
dns_provider = "none"
# domain_name is not requiredAccess your app via the CloudFront domain: https://d1234567890.cloudfront.net
Based on moderate traffic (1M requests/month, 10GB transfer):
| Service | Monthly Cost |
|---|---|
| Lambda (ARM64) | $0-2 (free tier covers most) |
| CloudFront | $0-1 (1TB + 10M requests free) |
| S3 Storage | $0-1 |
| DynamoDB | $0 (on-demand, low usage) |
| SQS | $0 (free tier) |
| ACM Certificate | $0 (free for CloudFront) |
| Route53 (optional) | $0.50 (hosted zone) |
Total: ~$0-5/month (mostly free tier)
# 1. Rebuild app
cd your-nextjs-app
npm run build
npx open-next@latest build
# 2. Update Lambda functions
cd ../terraform
terraform apply
# 3. Sync assets
ASSETS_BUCKET=$(terraform output -raw assets_bucket_name)
aws s3 sync ../your-nextjs-app/.open-next/assets s3://$ASSETS_BUCKET/ --delete
# 4. Invalidate CloudFront cache
DISTRIBUTION_ID=$(terraform output -raw cloudfront_distribution_id)
aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*"# Server function logs
aws logs tail /aws/lambda/my-app-server --follow
# Image optimization logs
aws logs tail /aws/lambda/my-app-image-optimization --followView in AWS Console:
- Request count
- Error rates (4xx, 5xx)
- Cache hit ratio
- Data transfer
If using external DNS and certificate validation is stuck:
-
Check validation records:
terraform output acm_certificate_domain_validation_options
-
Verify DNS records are created correctly in your DNS provider
-
Wait up to 30 minutes for DNS propagation
Check logs:
aws logs tail /aws/lambda/my-app-server --since 1hCommon issues:
- Missing environment variables
- S3 bucket permissions
- Memory/timeout limits
Verify:
- S3 bucket policy allows CloudFront OAC
- Assets are uploaded to S3
- Lambda function URLs are accessible
✅ Implemented by this module:
- S3 buckets are private (no public access)
- CloudFront uses Origin Access Control (OAC)
- HTTPS enforced (HTTP redirects to HTTPS)
- TLS 1.2+ minimum
- Lambda functions use least-privilege IAM roles
- No hardcoded credentials
- S3 versioning enabled by default
- CloudWatch logging enabled
See the examples/ directory for complete working examples:
examples/basic/- Simple deployment without custom domainexamples/route53/- Full setup with Route53 DNSexamples/cloudflare/- Using Cloudflare for DNSexamples/existing-certificate/- Bring your own certificateexamples/multi-region/- Multi-region deployment
| Name | Version |
|---|---|
| terraform | >= 1.5 |
| aws | >= 5.0 |
| archive | >= 2.0 |
| Name | Version |
|---|---|
| aws | >= 5.0 |
| aws.us_east_1 | >= 5.0 |
| archive | >= 2.0 |
- CloudFront Distribution
- Lambda Functions (4): Server, Image Optimization, Revalidation, Warmer
- S3 Buckets (2): Assets, Cache
- DynamoDB Table: Revalidation metadata
- SQS Queue: Revalidation queue (FIFO)
- ACM Certificate (optional)
- Route53 Record (optional)
- IAM Roles and Policies
- CloudWatch Event Rules
- CloudFront Functions
MIT
Contributions welcome! Please open an issue or PR.