CI/CD with Next.js on GitHub Actions: Should You Use "Deploy from a Branch" or "GitHub Actions"?

CI/CD with Next.js on GitHub Actions: Should You Use “Deploy from a Branch” or “GitHub Actions”?

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 or main)
  • 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:

FeatureDeploy from BranchGitHub Actions
Setup TimeSimpleSlightly longer
ControlLowFull 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.ymlfile 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!