Deploying a Static Next.js App to AWS S3 and CloudFront Using GitHub Actions


In the previous blog, we explored how to use the output: "export"
setting in Next.js to generate a static version of your app. This approach is ideal when you want to deploy your application to platforms that serve static files, such as AWS S3.
In this post, we’ll take the next step: deploying your exported static Next.js app to Amazon S3 behind a CloudFront CDN, using GitHub Actions for continuous deployment.
Why This Approach?
With output: "export"
, Next.js generates plain HTML, CSS, and JS files — no server needed. Hosting these files on S3 gives you scalable, low-cost storage, and serving them via CloudFront ensures fast delivery worldwide.
This works with both the App Router and the Pages Router, as long as your app is fully static (no server-side rendering or ISR).
Prerequisites
Before you begin, ensure you have the following:
A Next.js app with
output: "export"
configuredAn S3 bucket for hosting
A CloudFront distribution
An IAM role that GitHub can assume (OIDC enabled)
GitHub repository with Actions enabled
Step 1: Configure next.config.js
Update your next.config.js
file like this:
// next.config.js
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
trailingSlash: true,
};
export default nextConfig;
output: "export"
tells Next.js to statically export all pages.
Step 2: Build and Preview Locally
Run the following:
npm run build
The static site will be output to the out/
directory.
To preview locally:
npx serve out
Step 3: Set Up S3 Bucket
Go to the AWS S3 Console.
Create a new bucket (e.g.,
my-static-nextjs-app
).Keep it private.
Do not enable public access or static website hosting.
You will serve the site via CloudFront, which securely accesses this private bucket.
Step 4: Secure Access with CloudFront
There are two ways to grant CloudFront access to your private S3 bucket:
Option A: Using Origin Access Identity (OAI)
When creating your CloudFront distribution, enable OAI.
In your S3 bucket Permissions > Bucket Policy, add:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontReadAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity YOUR_OAI_ID"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
}
]
}
Replace:
YOUR_OAI_ID
with your CloudFront OAI IDYOUR_BUCKET_NAME
with your actual bucket name
Option B: Using Origin Access Control (OAC) – Recommended
Create a new CloudFront OAC
Attach it to your distribution
AWS will manage the bucket policy automatically
Step 5: Set Up GitHub Actions for Deployment
Create .github/workflows/deploy.yml
in your repo:
name: deploy
on:
push:
branches:
- main
env:
AWS_REGION: ap-south-1
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Build static site
run: npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_GITHUB_DEPLOY_ROLE
role-session-name: GitHubActionsSession
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to S3
run: aws s3 sync out s3://YOUR_BUCKET_NAME --delete
- name: Invalidate CloudFront cache
run: aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths '/*'
Replace the placeholders:
YOUR_ACCOUNT_ID
YOUR_GITHUB_DEPLOY_ROLE
YOUR_BUCKET_NAME
YOUR_DISTRIBUTION_ID
Your IAM role should have:
{
"Effect": "Allow",
"Action": [
"s3:*",
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
Make sure this role is trusted for GitHub OIDC authentication.
Step 6: Done! Push to Deploy
Push to the main
branch and watch your app deploy automatically:
npm run build
generates the static siteFiles are uploaded to your S3 bucket
CloudFront cache is invalidated so users see the latest version
Demo Repository
You can find the complete working example of this setup on GitHub:
🔗 GitHub Repository: https://github.com/harshitbansall/blog-demo-nextjs-client-apps
🔗 Deployment: https://d2tzkcpgro4cmj.cloudfront.net
This repository contains:
A minimal Next.js app configured with
output: 'export'
A GitHub Actions workflow to deploy to S3 and invalidate CloudFront cache
Sample IAM policy and trust relationship for GitHub OIDC
Instructions to reproduce the deployment from scratch
Notes
Ensure your Next.js app is fully static — no API routes, dynamic rendering, or server functions.
If you're using dynamic routes with
generateStaticParams
, make sure all necessary paths are statically generated at build time.trailingSlash: true
is required to map S3 folder-style routes correctly.
Conclusion
By combining the power of static exports in Next.js with S3 and CloudFront, you get a fast, reliable, and cost-effective way to deploy your frontend. With GitHub Actions, your deployments are now fully automated.
This architecture is especially useful for marketing sites, documentation, or SPAs — offering global performance without any servers to manage.
Subscribe to my newsletter
Read articles from Harshit Bansal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
