¯ cat aws-deployment.mdx
Deploying Next.js Applications to AWS: A Complete Guide
Step-by-step guide to deploying Next.js static sites to AWS using S3, CloudFront, and CI/CD pipelines
¯ cat aws-deployment.mdx
Step-by-step guide to deploying Next.js static sites to AWS using S3, CloudFront, and CI/CD pipelines
AWS offers a robust, scalable infrastructure for hosting Next.js applications. When using static export, you can leverage S3 and CloudFront for a cost-effective, high-performance deployment.
Next.js Build → S3 Bucket → CloudFront CDN → Route 53 (DNS)
↑
GitHub Actions (CI/CD)
Update your next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: {
unoptimized: true,
},
trailingSlash: true,
};
module.exports = nextConfig;
# Using AWS CLI
aws s3 mb s3://my-nextjs-app --region us-east-1
# Enable static website hosting
aws s3 website s3://my-nextjs-app \
--index-document index.html \
--error-document 404.html
Create a CloudFront distribution to serve your S3 content with HTTPS:
{
"DistributionConfig": {
"Origins": [
{
"DomainName": "my-nextjs-app.s3.amazonaws.com",
"Id": "S3Origin",
"S3OriginConfig": {
"OriginAccessIdentity": ""
}
}
],
"DefaultCacheBehavior": {
"TargetOriginId": "S3Origin",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
},
"Enabled": true
}
}
Point your domain to CloudFront:
# Create hosted zone
aws route53 create-hosted-zone --name example.com
# Create alias record
aws route53 change-resource-record-sets \
--hosted-zone-id ZXXXXXXXXXXXX \
--change-batch file://alias-record.json
Request a certificate via AWS Certificate Manager (ACM):
aws acm request-certificate \
--domain-name example.com \
--subject-alternative-names "*.example.com" \
--validation-method DNS \
--region us-east-1
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-nextjs-app",
"arn:aws:s3:::my-nextjs-app/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}
Create .github/workflows/deploy.yml:
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Deploy to S3
run: |
aws s3 sync out/ s3://my-nextjs-app \
--delete \
--cache-control max-age=31536000,public
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_ID }} \
--paths "/*"
This is more secure than using access keys:
# Create OIDC provider
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
# Create role with trust policy
aws iam create-role \
--role-name GitHubActionsRole \
--assume-role-policy-document file://trust-policy.json
Trust policy (trust-policy.json):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:username/repo:ref:refs/heads/main"
}
}
}
]
}
Set appropriate cache headers for different file types:
# HTML files - short cache
aws s3 cp out/ s3://my-nextjs-app \
--exclude "*" \
--include "*.html" \
--recursive \
--cache-control "max-age=300,public"
# Static assets - long cache
aws s3 cp out/ s3://my-nextjs-app \
--exclude "*" \
--include "*.js" --include "*.css" \
--recursive \
--cache-control "max-age=31536000,immutable,public"
Enable Brotli and Gzip compression in CloudFront:
Typical monthly costs for small-medium traffic:
Total: ~$3-18/month
Set up CloudWatch alarms:
# Monitor 4xx errors
aws cloudwatch put-metric-alarm \
--alarm-name high-4xx-rate \
--alarm-description "Alert on high 4xx error rate" \
--metric-name 4xxErrorRate \
--namespace AWS/CloudFront \
--statistic Average \
--period 300 \
--threshold 5 \
--comparison-operator GreaterThanThreshold
Keep previous deployments for easy rollback:
# Tag deployments
aws s3 cp out/ s3://my-nextjs-app-backup/v1.0.0/ --recursive
# Rollback if needed
aws s3 sync s3://my-nextjs-app-backup/v1.0.0/ s3://my-nextjs-app/ \
--delete
Deploying Next.js to AWS provides a scalable, cost-effective solution for static sites. With proper configuration and automation, you can achieve excellent performance and reliability.