Deploy Nextjs 13 app to AWS Lambda using AWS Serverless Application Model
Learn how to deploy your Next.js application to AWS Lambda using the AWS SAM framework and aws lambda web adapter. Simplify your deployment process and leverage the benefits of serverless architecture on AWS.
Next.js, the widely adopted React-based framework developed by Vercel, is a popular choice among companies for their web development needs. In this technical blog, we provide a detailed guide on how to deploy a server-side rendered Nextjs application on AWS Lambda using AWS SAM. AWS Lambda is a serverless service offered by Amazon Web Services that eliminates the need for infrastructure maintenance and can automatically scale depending on traffic. Although Next.js previously supported serverless deployment mode, this functionality was deprecated from version 12 onwards.
In this technical blog, we will be utilizing the AWS Serverless Application Model (SAM) for deployment and will provide a comprehensive walkthrough of each component associated with AWS SAM and its usage. The blog was written for the following versions of applications,
- Next.js: 13.2
- Node: 18.14.0
- Docker: 20.10.8
- AWS SAM CLI: 1.74.0
NextJs Serverless AWS Lambda Architecture
In this serverless architecture we are going to leverage following AWS Services,
- AWS Lambda
- AWS API Gateway
- AWS S3
- AWS Cloud Front
AWS Lambda Package for hosting the output files related to server-side rendering
The build process of Next.js generates build artifacts within the .next directory. In our system architecture, the files related to server-side rendering (SSR) are intended to be deployed to AWS Lambda and served via AWS API Gateway.
We are going to use AWS Lambda Web Adapter to run the web apps on Lambda.
NextJs Static Files Hosted in AWS S3 Bucket
For our system, the AWS S3 bucket is the designated hosting location for static files such as JS, CSS, and images. These files are served via CloudFront, which enables caching at edge locations and facilitates faster delivery of static content.
When routing traffic, CloudFront directs requests based on the URL. In our setup, any static file with a URL containing _next is directed to the S3 bucket, while all other requests are directed to AWS API Gateway by default.
AWS Lambda Web Adapter: Lambda Layer for running WebApps
In our solution, we utilize the AWS Lambda web adapter written in Rust, which is based on the AWS Lambda Rust Runtime, to run our web application. This adapter is capable of supporting AWS Lambda functions triggered by Amazon API Gateway Rest API, Http API (v2 event format), and Application Load Balancer. The Lambda Adapter serves to convert incoming events to HTTP requests and send them to the web application, then it converts the HTTP response back to a Lambda event response.
You can read more about the aws web lambda adapter in their github.
AWS SAM Configuration for NextJs Serverless deployment
Now, let's take a closer look at the AWS SAM template that we will use to deploy our Next.js application.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
NextJs Serverless AWS Lambda using SAM CLI
Parameters:
NextBucketName:
Type: String
Description: Bucket name for Next.js static resources
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Tracing: Active
Api:
TracingEnabled: True
Resources:
NextFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: ./
Handler: run.sh
Runtime: nodejs18.x
MemorySize: 512
Architectures:
- x86_64
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
RUST_LOG: info
PORT: 8080
Layers:
- !Sub 'arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:13'
Events:
RootPath:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /
Method: ANY
AnyPath:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
Metadata:
BuildMethod: makefile
NextBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref NextBucketName
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
LoggingConfiguration:
DestinationBucketName: !Ref NextLoggingBucket
LogFilePrefix: s3-access-logs
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
NextBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref NextBucket
PolicyDocument:
Id: NextBucketPolicy
Version: 2012-10-17
Statement:
- Action:
- 's3:GetObject'
Effect: Allow
Principal:
CanonicalUser: !GetAtt NextOriginAccessIdentity.S3CanonicalUserId
Resource: !Join
- ''
- - 'arn:aws:s3:::'
- !Ref NextBucket
- /*
NextLoggingBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub '${NextBucketName}-logs'
PublicAccessBlockConfiguration:
BlockPublicAcls : true
BlockPublicPolicy : true
IgnorePublicAcls : true
RestrictPublicBuckets : true
AccessControl: LogDeliveryWrite
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
DeletionPolicy: Delete
NextOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: OAI for Next static resources in S3 bucket
NextDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: nextS3Origin
DomainName: !GetAtt NextBucket.RegionalDomainName
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${NextOriginAccessIdentity}'
- Id: nextAPIGatewayOrigin
DomainName: !Sub '${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com'
OriginPath: '/Prod'
CustomOriginConfig:
HTTPSPort: '443'
OriginProtocolPolicy: https-only
Enabled: 'true'
Comment: 'Next.js Distribution'
HttpVersion: http2
DefaultRootObject: ''
DefaultCacheBehavior:
TargetOriginId: nextAPIGatewayOrigin
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad #
ForwardedValues:
QueryString: 'true'
Cookies:
Forward: all
Compress: 'true'
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
ViewerProtocolPolicy: redirect-to-https
MaxTTL: '31536000'
CacheBehaviors:
- PathPattern: '/_next/static/*'
TargetOriginId: nextS3Origin
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 #
AllowedMethods:
- GET
- HEAD
ForwardedValues:
QueryString: 'false'
Cookies:
Forward: none
Compress: 'true'
ViewerProtocolPolicy: https-only
- PathPattern: '/static/*'
TargetOriginId: nextS3Origin
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 #
AllowedMethods:
- GET
- HEAD
ForwardedValues:
QueryString: 'false'
Cookies:
Forward: none
Compress: 'true'
ViewerProtocolPolicy: https-only
PriceClass: PriceClass_100
ViewerCertificate:
CloudFrontDefaultCertificate: 'true'
Logging:
Bucket: !GetAtt NextLoggingBucket.RegionalDomainName
Prefix: 'cloudfront-access-logs'
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
NextApi:
Description: "API Gateway endpoint URL for Prod stage for Next function"
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/'
NextFunction:
Description: "Next Lambda Function ARN"
Value: !GetAtt NextFunction.Arn
NextFunctionIamRole:
Description: "Implicit IAM Role created for Next function"
Value: !GetAtt NextFunctionRole.Arn
NextBucket:
Description: "S3 bucket for Next static resources"
Value: !GetAtt NextBucket.Arn
NextDistribution:
Description: "CloudFront distribution for Next.js"
Value: !GetAtt NextDistribution.DomainName
Using the aforementioned SAM template, we will create a total of six AWS resources and are listed below,
AWS::Serverless::Function
- Created the AWs Lambda where the Next.js application runsAWS::S3::Bucket
- S3 bucket created for storing all the static files likes css,js and imagesAWS::S3::BucketPolicy
- S3 bucket policy to allow the access only to cloudfront.AWS::S3::Bucket
- S3 bucket to store all the cloudfront logsAWS::CloudFront::CloudFrontOriginAccessIdentity
- Cloud front origin access identity to provide the access for static resources in S3 bucketAWS::CloudFront::Distribution
- Cloud front distribution for the web app.
Lets review the lambda configuration details to understand it better as it is required for the deployment of next application.
AWS Lambda SAM Configuration
In the Lambda configuration, we utilize a custom runtime and the AWS Lambda Web Adapter Lambda layer to facilitate the execution of our Next.js application.
NextFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./
Handler: run.sh
Runtime: nodejs18.x
MemorySize: 512
Architectures:
- x86_64
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
RUST_LOG: info
PORT: 8080
Layers:
- !Sub 'arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:13'
Events:
RootPath:
Type: Api
Properties:
Path: /
Method: ANY
AnyPath:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
Metadata:
BuildMethod: makefile
For our custom runtime, we have selected nodejs18
as the programming language. When the Lambda function is invoked, this runtime executes the function's handler method which is a shell script.
The Metadata section of our configuration includes a makefile that outlines the build method for the custom runtime. During the sam build process, the makefile executes the following steps:
install
- This step installs all packages specified in the package.json file.
build
- This step initiates the Next.js build process to generate artifacts within the .next folder.
artifacts
- This step copies the contents of the .next folder to the .aws-sam/build folder.
# to install all the packages mentioned in the package.json
install:
npm install
# Run the Next.js Build Process to generate the artifacts in the `.next` folder
build:
npm run build
# Last steps to copy the contents of .next folder to artifacts directory under the .aws-sam folder
artifacts:
# Copy artifacts for deployment
cp -r .next/* $(ARTIFACTS_DIR)
cp run.sh $(ARTIFACTS_DIR)
build-NextFunction: install build artifacts
The AWS Lambda Web Adapter and its version is mentioned in the Layer information. When an the lambda is invoked by the API,this lambda web adapter will launch the application and perform the readiness check on http://localhost:8080/ every 10ms. It will start lambda runtime client after receiving 200 response from the application and forward requests to http://localhost:8080.
Within the Events
section of our configuration, we have set the Lambda invocation to be performed by the AWS API Gateway Rest API using the default path. This configuration creates an API Gateway and attaches it as a trigger for the Lambda function.
The handler file for this Lambda function is run.sh, which is a shell script containing the following command to invoke the Next server.js file:
#!/bin/bash
node standalone/server.js
Deploying your Next.js App to AWS
Now, let's review the deployment steps for the Next.js serverless Lambda function. These steps can be found within the deploy.sh
shell script.
#!/bin/bash
set -e
# Build the application using sam build
sam build
# Deploy the application to AWS using sam deploy
sam deploy --stack-name next-lambda-ssr --capabilities CAPABILITY_IAM --resolve-s3 --parameter-overrides NextBucketName=bucket-name --profile profile-name --region us-east-1
# Copy the js, css and other static files to s3 bucket
aws s3 cp .aws-sam/build/NextFunction/static/ s3://bucket-name/_next/static/ --recursive --profile profile-name
# Copy the public folder to s3 bucket as Next build dont include public folder during the build process
aws s3 cp public/static s3://bucket-name/static/ --recursive --profile profile-name
The first step of the deployment process is to run sam build, which generates the Next artifacts. As explained earlier, the sam build command executes the makefile and creates the artifacts within the .aws-sam/build
directory.
The second step is to run sam deploy, where we provide the S3 bucket name as a parameter along with the AWS profile, AWS region, and stack name. The parameters --capabilities CAPABILITY_IAM --resolve-s3
are passed to ensure that SAM takes care of the IAM permissions and S3 bucket creation for storing the CloudFormation templates of this stack. Once this step is completed successful,it should create all the services mentioned under the Resources
section of the AWS SAM template and upload the build artifacts to Lambda. The output of this step should return cloudfront url, rest api url and the bucket name. You can hit the cloudfront url on browser and see if you are getting the page loaded. Please ensure the user associated with the profile has the sufficent privilege to create all the resources mentioned in the SAM template.
The third and fourth steps involve copying the static files such as CSS, JS, and images to the S3 bucket that was created after running the sam deploy command. The fourth step is necessary because the Next.js build process does not copy the public folder to the .next
folder.
Sample Repo for Next.JS Serverless AWS Lambda
You can clone the sample repo for Next.js AWS Lambda implementation from github.
Furthermore, you can explore how to host nextjs static website on an aws s3 and cloudfront in this newly published blog post. Incase you are looking to deploy a nextjs app with the docker,please read our new blog post.