Using GitHub Pages to Build, Deploy, and Host Next.js

Last Updated

A guide to shoehorning Next.js projects into the GitHub Pages ecosystem, with GitHub Actions for building and deploying on push and Imgix for image hosting

A version of this article appeared on viget.com

Carpentry: a bridge on pontoons, details (top), and bird's-eye view (below). Engraving after Lucotte [?]
Carpentry: a bridge on pontoons, details (top), and bird's-eye view (below). Engraving after Lucotte [?]. Lucotte, Jacques-Raymond, approximately 1733-1804. Public Domain Mark. Source: Wellcome Collection.

Some Next.js details may be out of date

At the time of writing, Next.js was on v12.

Considering putting a Next.js site on GitHub Pages? It can be done, provided Next.js’s static export meets your needs. Netlify or Vercel are better choices for most people — like GitHub Pages they have free options, and unlike GitHub Pages they require almost no additional configuration to support Next.js. If you’re committed to GitHub Pages ecoystem, or if like me you just want to see what it takes, read on!

More Fun

I took this on as part of my adventure Comparing Heroku, Netlify, Vercel, and GitHub Pages for Node.js Projects.

In this tutorial we’ll

  1. enable GitHub pages for a Next.js project’s repo
  2. set up a GitHub Actions workflow to automatically build, export, and deploy the static site whenever main is pushed
  3. adjust Next.js’s options to fit with GitHub Actions URL structure
  4. and we’ll set up a personal Imgix image CDN to support next/image-optimized images.

The final GitHub Actions workflow file and Next.js config are at the end.

Configure GitHub

GitHub Pages must be turned on on a per-repo basis. When turning it on you can choose which branch to serve. The convention is to serve the branch gh-pages.

  1. In a browser, go to the repo in GitHub.
  2. In Settings > Pages > Source, select the gh-pages branch and click “Save”.

Configure GitHub Actions

We’ll use a GitHub action to deploy the Next.js project to GitHub Pages. The following is a boilerplate GitHub Actions workflow based on examples from GitHub. It configures the workflow to run when the branch main is pushed. It specifies a Node version, checks out main, installs Node dependencies with cacheing. Next we’ll configure the build and an external GitHub Pages workflow. Note that this workflow use npm ci (docs) as recommended by GitHub (ref). Save this to .github/workflows/some-descriptive-name.yml.

.github/workflows/build-and-deploy.yml
yaml
yaml
name: Node.js CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
# update the Node version to meet your needs
node-version: 16
cache: npm
- name: Build
run: |
npm ci
# configure the build here
- name: Deploy
# configure an external GitHub Pages deploy workflow here
yaml
name: Node.js CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
# update the Node version to meet your needs
node-version: 16
cache: npm
- name: Build
run: |
npm ci
# configure the build here
- name: Deploy
# configure an external GitHub Pages deploy workflow here

Configure build

GitHub pages only supports static sites, so we’ll use next export (docs). next export must be preceded by next build.

Remember, not all Next.js features are supported in static builds.

next build && next export will export a static copy of the site to the top-level directory out. Later, in the deploy step, we’ll duplicate out as the root directory in the branch gh-pages. But there’s a catch: next export puts CSS and JS in out/_next, and by default GitHub Pages ignores underscore-prefixed directories. The fix is to add a .nojekyll file as a sibling of the _next directory (GitHub Pages ignores underscore-prefixed directories by default because for historical reasons GitHub Pages gives special low-friction status to Jekyll and Jekyll ignores underscore-prefixed directories). Happily, creating that file programmatically takes no more than adding a touch command to the workflow.

The build part of the above workflow file becomes

.github/workflows/build-and-deploy.yml
yaml
yaml
# snip
- name: Build
run: |
npm ci
npm run build
npm run export
touch out/.nojekyll
yaml
# snip
- name: Build
run: |
npm ci
npm run build
npm run export
touch out/.nojekyll

And set up the npm scripts build and export as aliases to the next commands. (The below set-script commands are specific to npm. If you use something else —pnpm, yarn, etc— define them manually in your package.json’s scripts.)

shell
shell
npm set-script build "next build"
npm set-script export "next export"
shell
npm set-script build "next build"
npm set-script export "next export"

Configure deploy

We’ll offload the more involved work of deployment to a 3rd-party workflow. There are many workflows for deploying to GitHub Pages in the GitHub Marketplace (see for yourself). As of this writing, the most popular by far is JamesIves’s Deploy to GitHub Pages. A nice feature of this workflow is it does not require setting and using secret tokens. We simply configure it to deploy the directory out to the branch gh-pages.

The deploy part of the above workflow file becomes

.github/workflows/build-and-deploy.yml
yaml
yaml
# snip
- name: Deploy
# https://github.com/JamesIves/github-pages-deploy-action
uses: JamesIves/github-pages-deploy-action@4.4.0
with:
branch: gh-pages
folder: out
yaml
# snip
- name: Deploy
# https://github.com/JamesIves/github-pages-deploy-action
uses: JamesIves/github-pages-deploy-action@4.4.0
with:
branch: gh-pages
folder: out

Configure Next.js

Configure paths

Next.js’s 'next/image', 'next/link', and 'next/router' expect paths to be relative to /. GitHub Pages hosts sites at https://<user or org>github.io/<repo> Next.js needs to be configured to expect that /<repo>.

There are two relevant configuration options: basePath (docs) and assetPrefix (docs). Setting basePath to /<repo name> will result in GitHub Pages-friendly Links. And setting assetPrefix to /<repo name> will result in GitHub Pages-friendly Images.

next.config.js
js
js
const repo = 'change-me-to-your-repo'
const assetPrefix = `/${repo}/`
const basePath = `/${repo}`
module.exports = {
assetPrefix: assetPrefix,
basePath: basePath,
}
js
const repo = 'change-me-to-your-repo'
const assetPrefix = `/${repo}/`
const basePath = `/${repo}`
module.exports = {
assetPrefix: assetPrefix,
basePath: basePath,
}

Let’s apply this configuration only in the context of the GitHub Pages. We will use GitHub Actions to export a static copy of the site for GitHub Pages (details below), so we can take advantage of the environment variables GitHub automatically adds for us (docs). GITHUB_ACTIONS is true when GitHub Actions is running the process, and GITHUB_REPOSITORY is <owner>/<repo>

next.config.js
js
js
const isGithubActions = process.env.GITHUB_ACTIONS || false
let assetPrefix = ''
let basePath = '/'
if (isGithubActions) {
// trim off `<owner>/`
const repo = process.env.GITHUB_REPOSITORY.replace(/.*?\//, '')
assetPrefix = `/${repo}/`
basePath = `/${repo}`
}
module.exports = {
assetPrefix: assetPrefix,
basePath: basePath,
}
js
const isGithubActions = process.env.GITHUB_ACTIONS || false
let assetPrefix = ''
let basePath = '/'
if (isGithubActions) {
// trim off `<owner>/`
const repo = process.env.GITHUB_REPOSITORY.replace(/.*?\//, '')
assetPrefix = `/${repo}/`
basePath = `/${repo}`
}
module.exports = {
assetPrefix: assetPrefix,
basePath: basePath,
}

Configure next/image loading

next/image’s default “loader”, the function used to resolve image URLs, does not work when exporting a static build. Next.js supports several image host services (ref). Imgix’s free plan is a good solution for Next.js on GitHub Pages. We’ll store images in the Next.js project repo and use Imgix for optimization and delivery.

Add and upload images

  1. Put images in ./public or in a public subdirectory (for example ./public/images)

  2. Add the image files and commit.

  3. Push to GitHub.

Configure Next.js

Tell Next.js to load images from your Imgix source (docs).

next.config.js
js
js
module.exports = {
// …
images: {
loader: 'imgix',
path: 'the "domain" of your Imigix source',
},
}
js
module.exports = {
// …
images: {
loader: 'imgix',
path: 'the "domain" of your Imigix source',
},
}

Commit and push that.

Create an asset CDN

  1. Create a free Imgix account
  2. Create a new “Source”
    • For “Type” select “Web folder”
    • For “Base URL” enter https://<user or org>.github.io/<repo> (replace <user or org> and <repo> with the values for your Next.js project). If your images aren’t in the root directory, append to the path as necessary (e.g. https://<user or org>.github.io/<repo>/path/to/images)
    • Configure other settings to your liking, then save

You should now be able to view your images on Imgix. For example, if your repo has the file profile.jpg, your Imgix domain is https://me.imgix.net, and your Imgix base URL is https://<user or org>.github.io/<repo>, view it at https://me.imgix.net/profile.jpg. And next/image should find them too, both locally (requires an internet connection) and on GitHub Pages.

Putting it all together

Our complete GitHub Actions workflow file is

.github/workflows/build-and-deploy.yml
yaml
yaml
name: Node.js CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# https://github.com/actions/checkout
- uses: actions/checkout@v3
# a standard step for GitHub actions on Node
# https://github.com/actions/setup-node
- uses: actions/setup-node@v3
with:
# update the Node version to meet your needs
node-version: 16
cache: npm
- name: Build
run: |
npm ci
npm run build
npm run export
touch out/.nojekyll
- name: Deploy
# https://github.com/JamesIves/github-pages-deploy-action
uses: JamesIves/github-pages-deploy-action@4.4.0
with:
branch: gh-pages
folder: out
yaml
name: Node.js CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# https://github.com/actions/checkout
- uses: actions/checkout@v3
# a standard step for GitHub actions on Node
# https://github.com/actions/setup-node
- uses: actions/setup-node@v3
with:
# update the Node version to meet your needs
node-version: 16
cache: npm
- name: Build
run: |
npm ci
npm run build
npm run export
touch out/.nojekyll
- name: Deploy
# https://github.com/JamesIves/github-pages-deploy-action
uses: JamesIves/github-pages-deploy-action@4.4.0
with:
branch: gh-pages
folder: out

and our complete Next.js configuration is

next.config.js
js
js
const isGithubActions = process.env.GITHUB_ACTIONS || false
let assetPrefix = ''
let basePath = '/'
if (isGithubActions) {
const repo = process.env.GITHUB_REPOSITORY.replace(/.*?\//, '')
assetPrefix = `/${repo}/`
basePath = `/${repo}`
}
module.exports = {
assetPrefix: assetPrefix,
basePath: basePath,
images: {
loader: 'imgix',
path: 'the "domain" of your Imigix source',
},
}
js
const isGithubActions = process.env.GITHUB_ACTIONS || false
let assetPrefix = ''
let basePath = '/'
if (isGithubActions) {
const repo = process.env.GITHUB_REPOSITORY.replace(/.*?\//, '')
assetPrefix = `/${repo}/`
basePath = `/${repo}`
}
module.exports = {
assetPrefix: assetPrefix,
basePath: basePath,
images: {
loader: 'imgix',
path: 'the "domain" of your Imigix source',
},
}

With that committed and pushed to GitHub, the Imgix CDN configured and running, and GitHub Pages enabled for the Next.js project’s GitHub repo, every push to the branch main will deploy the Next.js app’s static export to GitHub Pages!

Articles You Might Enjoy

Or Go To All Articles