Setup a Next.js project in s3 to serve from cloudfront

Goal of this article:
Create and deploy a nextjs project in s3
Place a cloudfront distribution in front of the nextjs project
Serve the project from cloudfront
Bind the cloudfront distribution with a domain in Route53
Step1: Create a Next.js project locally
If you don't have one already, create a new Next.js project by applying the following command
npx create-next-app@latest
After that you need to choose from the options that nextjs provides
What is your project named? nextjs-hello-world
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`? No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*
Nextjs will take care of the rest. Once the project creation is complete go to the project folder
cd nextjs-hello-world
Once you are in the project folder, try to run the project.
yarn dev
you will see following kind of output
yarn run v1.22.22
$ next dev --turbopack
▲ Next.js 15.1.7 (Turbopack)
- Local: http://localhost:3000
- Network: http://192.168.0.100:3000
✓ Starting...
✓ Ready in 717ms
Go to your browser and type localhost:3000 to see if the project is actually running. If you see following page appear in your browser window you are all set
Step 2: Build the Next.js project
Go to the project folder and open the folder in your favorite IDE, in my case its VS code. Go to the next.config.ts to update the build settings. Paste the following codes in the config file, the main part is the config object
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: 'export',
/* config options here */
};
export default nextConfig;
After that go to the terminal and run the build command.
yarn build
Once the build is done you will see following kind of output.
yarn run v1.22.22
$ next build
▲ Next.js 15.1.7
Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (7/7)
✓ Collecting build traces
✓ Exporting (5/5)
✓ Finalizing page optimization
Route (app) Size First Load JS
┌ ○ / 5.57 kB 111 kB
└ ○ /_not-found 979 B 106 kB
+ First Load JS shared by all 105 kB
├ chunks/4bd1b696-20882bf820444624.js 52.9 kB
├ chunks/517-0d7e8c6ba2452810.js 50.5 kB
└ other shared chunks (total) 1.88 kB
Route (pages) Size First Load JS
┌ ○ /rabbyshop 274 B 93.5 kB
└ ○ /rubelshop 273 B 93.5 kB
+ First Load JS shared by all 93.2 kB
├ chunks/framework-f3265d9e65bd9924.js 57.5 kB
├ chunks/main-1805dd34a5086dd9.js 33.8 kB
└ other shared chunks (total) 1.86 kB
○ (Static) prerendered as static content
Once the build is finished you will see a out folder created inside your project folder
This indicates that the build is complete
Step 3: Create an S3 Bucket
Login to AWS console, write s3 in the search bar and select s3 from result. Now click create bucket in your desired region, I am using us-east-1 as the region for this purpose.
Enter your desired name for the bucket
Uncheck "Block all public access" and acknowledge the warning.
Leave other settings as default and click "Create bucket."
Your bucket will be created in a while
Step 4: Upload the build folder to s3
I will be using AWS console for this purpose. Select your s3 bucket and go inside the bucket. Click the upload button
Click the add folder button
It will prompt to select the desired folder
Once you selected the folder , it will show you the summary
Finally hit the upload button to start the upload
Once finished AWS console will tell you about that
Click the finish button to go back inside the bucket
Step 5: Create a CloudFront Distribution:
Type cloudfront in the search bar and select cloudfront. Once you are in the cloudfront distribution page , click create new distribution
Select the newly created s3 as the origin for cloudfront distribution
Once selected, review the rest of the origin settings
Review the cache behavior. In my case, I selected followings
Compress Objects Automatically: Choose "Yes."
Viewer Protocol Policy: Choose "Redirect HTTP to HTTPS."
Allowed HTTP Methods: Select "GET, HEAD, OPTIONS."
And left the rest as it is
For now I select no security protection and choose an alternative domain name and SSL certificate for the setup. Setting up SSL is a requirement, without it you will not be able to create the distribution
Leave the rest and click, create distribution
It will take some time to create the distribution, wait for the deployment of cloudfront distribution is complete.
Once the deployment is complete go inside the distribution, copy the distribution domain name
Paste the domain url in the browser and hit enter. You will see something similar to following.
Step 6: Configure the permission for cloudfront to access s3
Now that although the creation of cloudfront distribution is complete, we are getting the access denied error. The "AccessDenied" error we're seeing indicates that CloudFront is unable to access the files in our S3 bucket. This is a common issue when setting up CloudFront with S3, and it's usually related to bucket permissions.
Now to solve this access error , go to the cloudfront distribution, go to the origin tab, select the origin and click edit. Under origin access select origin access control settings. Click create new OAC
Write the description if you want , I left it blank. Choose the recommended sign requests option and click create.
Once created, select the created OAC as the origin access control, you will see something similar to below screenshot. Now click the “copy policy“ button, this is the bucket permission policy we will add to the bucket.
Now to go the s3 bucket, click the permission tab
Under the permission tab, go to the bucket policy, click edit and paste the policy just copied from cloudfront distribution previously. After that click “Save chnages“. Now we have updated the permission of s3 to be accessed from cloudfront.
Now go to the cloudfront distribution origin where we left and click save changes. After that the permission is modified. Wait for the cloudfront distribution to get deployed.
Once deployed, we can access the index.html file by visiting cloudfront domain url. Well not exactly. You will see same access denied screen once again
Interesting fact is that if we go to cloudfront_domain_url/out/index.html then we will see the content
Step 7 : Move the contents of s3 to root location and update the root object in cloudfront
Why the content is not appearing when visiting the cloudfront domain url ? Because we have uploaded the out folder to s3 and all the content is residing inside that out folder. That’s why. Now go to the s3 bucket move all the files inside out folder to the root path of the s3.
Now lets try once again. We will get the access denied error once again. But if we enter the url like this
cloudfront_domain_url/index.html content will appear. This indicates that cloudfront is not understanding that index.html is the root file/object when someone hits the distribution url. We need to fix this
Go to the cloudfront distribution, click edit in the settings section
Go the default root object and enter “index.html“ as the value of it, click “Save changes“ and wait for the cloudfront distribution to complete the deployment.
Once the deployment is complete if you now visit the cloudfront distribution url you will see the content is appearing just by visiting the url , without adding the root file name. Voila
Now our setup is done. Cloudfront distribution is serving static content from s3 properly. If you read the content of this article carefully you might notice that at the build step we have built the static files not some dynamic routing. That raises the following question
Can a Next.js (SSR) app be hosted on AWS S3? Why?
No, a Next.js app using Server-Side Rendering (SSR) cannot be hosted directly on AWS S3. Here's why: 1. Nature of SSR: SSR involves generating HTML on the server for each request. This means that the server must have the capability to run Node.js (or another server-side environment) to handle incoming requests, execute server-side code, and render pages dynamically. 2. AWS S3 Limitations: AWS S3 is a static file storage service, which means it can only serve static content (like HTML, CSS, JavaScript, images, etc.) and does not support running server-side code or handling dynamic requests.
By keeping the nuts and bolts in mind, if you want to serve static content from next.js project you can do so by uploading the build to s3 and serve it from cloudfront.
Cheers for yourself for the patience to read this out so long.
Subscribe to my newsletter
Read articles from Rabby Khan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
