Next.js Tutorial

How to Deploy a Next.js Static Site to GitHub Pages

A practical guide to exporting a static Next.js site, publishing it to GitHub Pages, and avoiding the most common deployment mistakes.

Back to tutorials

GitHub Pages works best with plain static files, which makes it a good match for a Next.js site that uses static export. The important detail is that not every Next.js feature works in export mode. Server-side rendering, dynamic server functions, and API routes do not belong in this workflow. If your site is mostly content, tools, landing pages, and static routes, export mode is often the simplest deployment path.

This tutorial is written for developers who want a reliable GitHub Pages setup, not just a one-time lucky deploy. It covers the core configuration, build output, publishing flow, and the issues that usually break CSS, assets, or deep links. If you are publishing a public resource site, pair this with the deployment readiness audit once the build is working.

When static export is the right fit

Static export is the right fit when every page can be generated at build time. That includes portfolio pages, resource hubs, documentation-style sections, and many marketing sites. It is not the right fit when you depend on server-only data fetching, runtime authentication checks, or API routes hosted in the same app.

The simplest mental model is this: if `next build` can write every route to the `out` directory as static files, GitHub Pages can serve it.

What you need before deployment

  • A working Next.js app that does not rely on server-side runtime features.
  • A GitHub repository with Pages hosting available.
  • A build that already passes locally.
  • A clear branch strategy for source and published output.

Step 1: Configure Next.js for export

Next.js export mode is enabled in `next.config.mjs` using `output: "export"`. That tells Next.js to generate static files into the `out` directory at build time.

config
const nextConfig = {
  output: "export",
  images: {
    unoptimized: true,
  },
};

export default nextConfig;

The `images.unoptimized` setting matters because GitHub Pages cannot run the built-in Next.js image optimizer. If you use `next/image`, this avoids broken image behavior in static hosting.

Step 2: Handle images and public assets

Static hosting is unforgiving about asset paths. If your CSS, JS, or images break after deployment, the root cause is often a base path issue, an incorrect custom domain assumption, or assets that depend on server-side behavior. For custom domains, using root-relative asset paths usually works well. For repository pages, a base path may be required.

If the site will live on a custom domain like `rajib.uk`, keep the configuration aligned with that final destination. If it lives under a repo path, configure the base path intentionally instead of discovering the mistake after deployment.

Step 3: Generate the out directory

Run the production build locally first:

command
npm run build

In export mode, Next.js writes the final static site into the `out` directory. That is the folder GitHub Pages needs, directly or indirectly. Check that the routes you care about exist inside `out` and that important pages like `/`, `/tutorials`, and your legal pages are all present.

Step 4: Publish to GitHub Pages

One common workflow is to publish the `out` directory to a dedicated `gh-pages` branch. That keeps the source branch clean and lets GitHub Pages serve the static output directly.

package.json
"predeploy": "npm run build",
"deploy": "node -e \"const fs=require('fs'); fs.writeFileSync('out/.nojekyll',''); fs.writeFileSync('out/CNAME','example.com\\n');\" && gh-pages -d out --dotfiles -r https://github.com/your-user/your-repo.git"

The `.nojekyll` file matters because GitHub Pages can otherwise treat underscored directories differently, which can break the `/_next` asset paths your exported site depends on.

Step 5: Add a custom domain

If you use a custom domain, add a `CNAME` file to the published output so GitHub Pages knows which hostname should resolve to the site. Many deployment scripts write this file automatically during the publish step.

CNAME
rajib.uk

You also need the matching DNS records on your domain provider. The exact records depend on whether you use an apex domain or a subdomain, but the important point is consistency: the domain in DNS, the GitHub Pages configuration, and the CNAME file should all agree.

Common mistakes

  • Using server-only Next.js features in an export workflow.
  • Forgetting `images.unoptimized` when using `next/image`.
  • Publishing without `.nojekyll`, which can break `/_next` assets.
  • Mixing up the source branch and the published branch.
  • Testing only `/` and forgetting to check deep routes.

If a deployed page looks like plain HTML with no CSS, that usually means the page loaded but the `/_next` assets did not. Check the network requests first, then confirm your `.nojekyll` file, base path assumptions, and hosting path.

Command cheat sheet

commands
npm run build
npm run deploy
git push origin main
ls out
cat out/CNAME

Once the site is live, revisit the tutorials hub and the security header planner if you want to harden the public-facing setup further.