Use GitHub Actions to Deploy a Next.js SSG Site
Let's use GitHub Actions to create a basic deployment pipeline for a Next.js SSG site hosted on Github Pages.
For reference, here’s the Github repo for the code used in this article.
Optional Readings
This article will be laser-focused on setting up GitHub Actions, so I may gloss over technical details I consider ancillary. Here are some background readings if you're interested:
-
The docs on how to build an SSG site with Next.js. For this article, AYNTK is to use the
next build
andnext export
commands to generate the files for a static site. -
The docs on how to publish a static site on GitHub Pages. For this article, AYNTK is to commit and push the build files within the
docs
folder in the project root directory to deploy the site. -
The definitions of the different components of a GitHub Actions pipeline.
Big Picture
Let's create a GitHub Actions pipeline to automate the following steps:
- Check out the GitHub repo
- Setup Node.js
- Install dependencies (or use cache if no changes)
- Build the files for the static site (or use cache if no changes)
- Cache dependencies and source files
- Commit and push the build files to the repo
Create a Workflow
GitHub Actions uses YAML syntax to define the events, jobs, and steps. These YAML files are stored in the code repository, in a directory called .github/workflows
. So let's create a YAML file to define the deployment workflow.
# ./.github/workflows/deploy.workflow.yml
name: deploy-workflow
on: [push] # [A]
jobs:
deploy-job:
runs-on: ubuntu-latest # [B]
steps:
- uses: actions/checkout@v2 # [C]
- uses: actions/setup-node@v2 # [D]
with:
node-version: '12'
Annotations of the code comments above:
[A] Pushing a commit to the GitHub repo is the event that triggers the workflow.
[B] Configures the job to run on a fresh Ubuntu Linux virtual machine hosted by GitHub (aka the Runner).
[C] The action to check out the repo and download the code into the Runner.
[D] The action to install Node.js in the Runner, and allow us to run npm
commands.
Next, add the step to install the project dependencies in the Runner, which include the next
package.
# ./.github/workflows/deploy.workflow.yml
name: deploy-workflow
on: [push] # [A]
jobs:
deploy-job:
runs-on: ubuntu-latest # [B]
steps:
- uses: actions/checkout@v2 # [C]
- uses: actions/setup-node@v2 # [D]
with:
node-version: '12'
+ - run: npm install # [E]
[E] Ah, our old friend.
Now that the dependencies are installed, let’s add the step to build the static files.
# ./.github/workflows/deploy.workflow.yml
name: deploy-workflow
on: [push] # [A]
jobs:
deploy-job:
runs-on: ubuntu-latest # [B]
steps:
- uses: actions/checkout@v2 # [C]
- uses: actions/setup-node@v2 # [D]
with:
node-version: '12'
- run: npm install # [E]
+ - run: npm run build # [F]
[F] What the heck is build
you ask? It’s a script defined in package.json
that runs:
next build && next export -o docs
See the docs. TL;DR these are the Next.js commands to build the files for the SSG site, and export them to the docs
folder.
Lastly, add the step to commit and push the updates to the docs
folder to the repo.
# ./.github/workflows/deploy.workflow.yml
name: deploy-workflow
on: [push] # [A]
jobs:
deploy-job:
runs-on: ubuntu-latest # [B]
steps:
- uses: actions/checkout@v2 # [C]
- uses: actions/setup-node@v2 # [D]
with:
node-version: '12'
- run: npm install # [E]
- run: npm run build # [F]
+ - uses: stefanzweifel/git-auto-commit-action@v4 # [G]
+ with:
+ commit_message: Automated publish
[G] This action will commit changes made in the Runner environment, and push the commit to the GitHub repo. The default commit message will be “Automated publish”.
Now, pushing a change to the repo will automatically deploy my SSG site to GitHub Pages. 🎉
I can now stare at the pipeline in the Actions tab of my GitHub repo.
Caching
I realized that the job runs a fresh npm install
every time I push a commit. So let's introduce caching so that a fresh install occurs only when package-lock.json
changes.
# ./.github/workflows/deploy.workflow.yml
name: deploy-workflow
on: [push] # [A]
jobs:
deploy-job:
runs-on: ubuntu-latest # [B]
steps:
- uses: actions/checkout@v2 # [C]
- uses: actions/setup-node@v2 # [D]
with:
node-version: '12'
+ - uses: actions/cache@v2 # [H]
+ with:
+ path: ${{ github.workspace }}/node_modules
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
- run: npm install # [E]
- run: npm run build # [F]
- uses: stefanzweifel/git-auto-commit-action@v4 # [G]
with:
commit_message: Automated publish
[H] This action caches the node_modules
folder across builds, and makes the Runner use the cache as long as package-lock.json
doesn’t change.
Additionally, noticed that next build
issues the following warning about lack of caching.
warn - No build cache found. Please configure build caching for faster rebuilds.
Read more: https://err.sh/next.js/no-cache
The warning links you to the answer to fix it. Love it.
# ./.github/workflows/deploy.workflow.yml
name: deploy-workflow
on: [push] # [A]
jobs:
deploy-job:
runs-on: ubuntu-latest # [B]
steps:
- uses: actions/checkout@v2 # [C]
- uses: actions/setup-node@v2 # [D]
with:
node-version: '12'
- uses: actions/cache@v2 # [H]
with:
- path: ${{ github.workspace }}/node_modules
+ path: |
+ ${{ github.workspace }}/node_modules
+ ${{ github.workspace }}/.next/cache # [I]
- key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
+ # Generate a new cache whenever packages or source files change.
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js') }}
+ # If source files changed but packages didn't, rebuild from a prior cache.
+ restore-keys: |
+ ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm install # [E]
- run: npm run build # [F]
- uses: stefanzweifel/git-auto-commit-action@v4 # [G]
with:
commit_message: Automated publish
[I] Next.js stores its cache in the .next/cache
directory. This will persist the cache across builds for faster application rebuilds. E.g., if I only updated my codebase but not the dependencies, this avoids re-bundling the dependencies.
Improved deployment time by about ~30%! 🥰