If you’ve ever tried to deploy a Next.js app to GitHub Pages and got stuck deciding between “Deploy from a branch” and “GitHub Actions”, you’re not alone. I recently went through this while setting up CI/CD for a Next.js frontend project and learnt some valuable lessons that could save you hours of confusion.
In this article, I’ll break down what each deployment method means, why GitHub Actions is often the better choice (especially for Next.js), and how to set it up the right way.
My Setup: CI/CD with Next.js and GitHub Actions
I had successfully created a CI/CD pipeline using GitHub Actions to automate the process of:
- Installing dependencies
- Building the app
- Exporting static files (when applicable)
- Deploying to GitHub Pages
Everything worked perfectly when I selected “GitHub Actions” under GitHub Pages settings. But when I tried switching to “Deploy from a branch”, the deployment failed, and it left me wondering: Why?
Understanding the Two Deployment Options
In your GitHub repository’s Settings → Pages, you’ll see this under “Build and deployment”:
Source:
🔘 Deploy from a branch
🔘 GitHub Actions
Let’s break these down:
Deploy from a Branch
This method is very simple:
- GitHub Pages takes the contents of a branch (like
gh-pages
ormain
) - It assumes those contents are already static files (HTML, CSS, JS)
Great for:
- Static sites with no build step
- Manually exported content (e.g., using
next export
)
Limitations:
- Not ideal for dynamic frameworks like Next.js unless you’re exporting (
next export
) and manually pushing the output to a branch.
Deploy Using GitHub Actions
This is where you let GitHub Actions build and deploy your project automatically after every commit.
Great for:
- Frameworks like Next.js, React, Vue
- Projects that require a build step or CI/CD workflow
- Teams that want version control, automation, and flexibility
Bonus: You can even run tests, lint checks, or performance audits during deployment.
Why “Deploy from a Branch” Doesn’t Work for Next.js (Out of the Box)
Next.js apps are not static by default. They include:
- Server-side rendering (SSR)
- API routes
- Dynamic routing
If you simply push your /pages
or /src
folder to GitHub, GitHub Pages won’t know how to build it, and you’ll get a blank page or 404.
To use “Deploy from a branch”, you’d need to:
npm run build
npm run export
Then push the /out
directory to a gh-pages
branch, which is tedious and error-prone.
Why I Recommend Sticking With GitHub Actions
Since GitHub Actions worked perfectly for my setup, I left it that way, and here’s why you should too:
Feature | Deploy from Branch | GitHub Actions |
---|---|---|
Setup Time | Simple | Slightly longer |
Control | Low | Full control (build, test, deploy) |
Works with Next.js? | ❌ No (unless exported manually) | ✅ Yes |
Automation | ❌ Manual pushes | ✅ Automatic on every commit |
Customization | ❌ None | ✅ Full flexibility |
How to Set It Up (Step-by-Step)
If you’re using GitHub Actions to deploy your Next.js app, your .github/workflows/nextjs.yml
file might look like this:
name: Deploy Next.js site to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: ${{ steps.detect-package-manager.outputs.manager }}
- name: Setup Pages
uses: actions/configure-pages@v5
with:
# Automatically inject basePath in your Next.js configuration file and disable
# server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
#
# You may remove this line if you want to manage the configuration yourself.
static_site_generator: next
- name: Restore cache
uses: actions/cache@v4
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Don’t forget to set your GitHub Pages source to:
Settings → Pages → Build and Deployment → Source:
GitHub Actions
Final Thoughts
If you’re building with Next.js, avoid the “Deploy from a branch” option unless you’re explicitly using next export
and handling deployment manually. GitHub Actions gives you far more power, automation, and scalability, especially as your project grows or integrates with backend services.
So yes, it’s perfectly okay (and actually best) to leave it set to “Deploy from GitHub Actions”.
Useful Resources
Over to You
Have you struggled with deploying your frontend on GitHub Pages before? Did switching to GitHub Actions solve it for you too?
Feel free to share your story in the comments below.
Thanks for reading…
Happy Coding!