Deploy Nextjs Static Website to AWS S3 and Cloudfront
Learn how to deploy your Next.js static website to AWS S3 and Cloudfront in just a few simple steps. With this guide, you'll be able to optimize your website's speed and reliability, while reducing your hosting costs.
By deploying a Next.js static website to AWS S3 and Cloudfront, you can significantly enhance the performance and scalability of your website, while also decreasing your hosting expenses. In this tutorial, we will take you through the necessary steps to deploy your Next.js website to AWS S3 and Cloudfront. This blog is written with the following versions:
- Next 13.3.0
- Node 18
- AWS SAM CLI 1.76
Next.js Static Hosting in AWS S3 and Cloudfront
Lets review the architecture for nextjs static website hosted on the AWS S3 and Cloudfront,
For this particular architecture, we'll utilize a solitary AWS S3 bucket to store all the necessary files, including JavaScript, CSS, images, and the index.html files generated during the Next.js build process. To allow CloudFront to access the bucket content using origin policy and activate the static website hosting feature on the bucket, we'll need to modify the bucket permissions. Furthermore, AWS CloudFront will be employed to sit in front of the S3 bucket and help with edge caching of the files. By caching the files on CloudFront, we can limit read calls to S3 and boost our website's performance and security.
Next.js static export for Static Website Hosting
For next.js version 13.2 and below, the static export can be done in Next using the commands next build && next export
.
Running this command generate an out
directory and the process is follows,
next export
builds an HTML version of your app. During next build
, getStaticProps and getStaticPaths will generate an HTML file for each page in your pages directory (or more for dynamic routes). Then, next export
will copy the already exported files into the correct directory. getInitialProps will generate the HTML files during next export instead of next build.
Starting with Next 13.3, the next export
will be deprecated and static export feature is enabled on the next.config.js
by including output attribute as export output: 'export'
During the next build
process,it should generate the out
directory containing the HTML/CSS/JS static assets. We can customize the output directory by mentioning the output directory if required in the next.config.js
file using attribute distDir: 'dist'
AWS S3 and Cloudfront Creation using AWS SAM
Now lets talk about the AWS SAM template and see how we can deploy the static contents. In this tutorial, we are creating AWS S3, Cloudfront, Cloudfront function and then create a A record in route 53 for the cloudfront entry.
#### AWS SAM Template. Please replace the parameters where you see <replace-me> text
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
# Template Information
Description: "Nextjs Website Hosted in S3 and Cloudfront"
# Template Parameters
Parameters:
DomainName:
Type: String
Description: "The domain name of website"
Default: <replace-me> # Replace me
HostedZoneId:
Type: String
Description: "The Route53 hosted zone ID used for the domain"
Default: <replace-me> # Replace me
AcmCertificateArn:
Type: String
Description: "The certificate arn for the domain name provided"
Default: <replace-me> # Replace me
IndexDocument:
Type: String
Description: "The index document"
Default: "index.html"
ErrorDocument:
Type: String
Description: "The error document, ignored in SPA mode"
Default: "404.html"
RewriteMode:
Type: String
Description: "The request rewrite behaviour type"
Default: "STATIC"
AllowedValues:
- STATIC
- SPA
CloudFrontPriceClass:
Type: String
Description: "The price class for CloudFront distribution"
Default: "PriceClass_100"
AllowedValues:
- PriceClass_100
- PriceClass_200
- PriceClass_All
# Resources create conditions
Conditions:
IsStaticMode: !Equals [!Ref RewriteMode, "STATIC"]
IsSPAMode: !Equals [!Ref RewriteMode, "SPA"]
# Template Resources
Resources:
DnsRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref HostedZoneId
Name: !Ref DomainName
Type: A
AliasTarget:
DNSName: !GetAtt Distribution.DomainName
HostedZoneId: "Z2FDTNDATAQYW2" # CloudFront
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref DomainName
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref Bucket
PolicyDocument:
Statement:
- Effect: "Allow"
Action: "s3:GetObject"
Resource: !Sub "arn:aws:s3:::${Bucket}/*"
Principal:
AWS: !Sub 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${OriginAccessIdentity}'
OriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref AWS::StackName
RewriteRequestStaticFunction:
Condition: IsStaticMode
Type: AWS::CloudFront::Function
Properties:
Name: !Sub "${AWS::StackName}-req-static"
AutoPublish: true
FunctionCode: !Sub |
function handler(event) {
var request = event.request;
var uri = request.uri
if (uri.endsWith('/')) {
request.uri += '${IndexDocument}';
} else if (!uri.includes('.')) {
request.uri += '/${IndexDocument}';
}
return request;
}
FunctionConfig:
Comment: !Sub "rewrite all paths to /${IndexDocument}"
Runtime: cloudfront-js-1.0
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Comment: !Ref AWS::StackName
DefaultRootObject: !Ref IndexDocument
HttpVersion: http2
CustomErrorResponses:
- ErrorCachingMinTTL: 86400
ErrorCode: 403 # object not found in bucket, then return 404 status with 404.html file
ResponseCode: 404
ResponsePagePath: !Sub "/${ErrorDocument}"
Origins:
- DomainName: !Sub "${Bucket}.s3.${AWS::Region}.amazonaws.com"
Id: bucketOrigin
S3OriginConfig:
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${OriginAccessIdentity}
DefaultCacheBehavior:
Compress: true
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: bucketOrigin
ForwardedValues:
QueryString: false
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
FunctionAssociations:
- EventType: viewer-request
FunctionARN: !GetAtt RewriteRequestStaticFunction.FunctionMetadata.FunctionARN
# FunctionARN: !If [IsStaticMode, !GetAtt RewriteRequestStaticFunction.FunctionMetadata.FunctionARN, !GetAtt RewriteRequestSpaFunction.FunctionMetadata.FunctionARN]
PriceClass: !Ref CloudFrontPriceClass
Aliases:
- !Ref DomainName
ViewerCertificate:
AcmCertificateArn: !Ref AcmCertificateArn
SslSupportMethod: sni-only
# Template Outputs
Outputs:
BucketName:
Description: "The S3 bucket name where HTML files need to be uploaded"
Value: !Ref Bucket
CloudFrontDistribution:
Description: "The CloudFront distribution in front of the S3 bucket"
Value: !Ref Distribution
WebsiteUrl:
Description: "The website URL"
Value: !Sub "https://${DomainName}/"
NextJs 13 Static Export Deployment process to host in AWS S3 and cloudfront
With our configuration now complete, the subsequent step is to employ the SAM CLI command to deploy the infrastructure and then transfer the content from the public folder to S3. To accomplish this, we can create a shell script.
npx next build
sam validate
sam deploy --profile aws-profile --region aws-region
aws s3 cp out s3://example.com --recursive --cache-control max-age=31536000 --profile aws-profile --region aws-region
These are the steps which happens when you execute this shell script,
npx next build
will generate theout
folder with all the statically generated html pages and the assocaitedjs
andcss
file.sam validate
will validate the sam template yaml and wont proceed with deployment if the template is invalid.sam deploy
will create all the aws resources. In this case it will create S3,cloudfront and cloufront functionaws s3 cp
is the last step and this will copy all the contents of theout
folder to the s3 bucket. With this command we also set the cache-control headers for thejs
,css
and image files , so that these files are cached in the client browser for subsequent requests and improve the performance.
Cloudfront Function for Redirection
Because of the way in which AWS S3 organizes files, accessing a webpage without the trailing slash may result in a status of 302 being returned, which can harm the SEO. Excerpts from AWS documentation,
If you create a folder structure in your bucket, you must have an index document at each level. In each folder, the index document must have the same name, for example, index.html. When a user specifies a URL that resembles a folder lookup, the presence or absence of a trailing slash determines the behavior of the website. For example, the following URL, with a trailing slash, returns the photos/index.html index document.
http://bucket-name.s3-website.Region.amazonaws.com/photos/
However, if you exclude the trailing slash from the preceding URL, Amazon S3 first looks for an object photos in the bucket. If the photos object is not found, it searches for an index document, photos/index.html. If that document is found, Amazon S3 returns a 302 Found message and points to the photos/ key. For subsequent requests to photos/, Amazon S3 returns photos/index.html. If the index document is not found, Amazon S3 returns an error.
To address this issue, we have a cloudfront function associated with the cloudfront that can manage the redirection and issue the appropriate status code(200 OK in this case) when files are accessed without a trailing slash. For further information on how S3 manages files, please refer to this AWS article.
Sample Repo for NextJs 13 app that can be hosted as a static site in S3
You can access the next 3 static hosted s3 website sample repo here. This sample repo contains a simple nextjs app and also the aws sam template configuration which can be used to create aws infrastructures and deploy the changes to these infrastructure.
Furthermore, you can explore how to host nextjs ssr website using aws lambda 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.