I publish several of my open-source applications on GitHub Pages. This is a convenient way to demonstrate a built version of a code base from a repository without requiring a separate cloud service like Azure for hosting. Previously I have used travis-ci.org for building and deploying the demo applications but as travis-ci.org has migrated to travis-ci.com, the free open-source plan became very unclear so I searched for an alternative. I ended up using GitHub Actions for build & deploy and this works nicely with GitHub Pages.
This blog post describes how I have set up GitHub Actions for building my Angular and React applications and deploying them to GitHub Pages.
Migrating from Travis
I have been using travis-ci.org for many years for building, testing and deploying my open-source projects. It used to work great and I have invested quite a lot of time in this platform. Since June 2021, travis-ci.org has ceased and you have to migrate to travis-ci.com. I started doing that for my projects but soon realized that this is not the best option for me. The platform is still free for open-source projects but now you have a limited amount of credits at your disposal and you need to manually beg for additional credits to keep your projects running. So I went looking for alternatives.
I now use Azure DevOps for building projects that deploys as services hosted in Azure. But for more simple projects with only a front-end that is deployed to GitHub Pages, I turned to GitHub Actions to perform the CI/CD work.
What is GitHub Pages?
GitHub Pages is a hosting service that lets you create a website directly from a GitHub repository, see pages.github.com for details. You can host any static web content in GitHub pages but I mostly use GitHub Pages for serving demo version of my open-source Angular and React applications. You can have one GitHub pages site per GitHub account but have unlimited project sites under the main url.
What is GitHub Actions?
GitHub Actions is a feature in GitHub that lets you automate software development flows within your GitHub account. There is a myriad of functionality that can be used but I primarily use the features for continuous integration and continuous deployment.
GitHub actions is based around a workflow that is triggered by an event. An event trigger can be something happening within the GitHub repository (e.g. a push to to a repository) or something external that triggers the workflow with the help of a webhook. The workflow contains jobs which is divided into steps that perform actions for e.g. building, testing and deploying your application.
Testing GitHub pages
As a first GitHub Pages example, let’s create a branch in an existing repository and use it for publishing a static index.html file to a sub project in your GitHub pages site. You can publish to GitHub pages from any branch, but to follow the conventions, I use a gh-pages branch.
For an existing repository, create an empty gh-pages branch. If you want a new branch that has a separate history from the main/master branch, you can use git checkout –orphan (this example is adapted from https://gist.github.com/ramnathv/2227408):
git clone <YOUR REPOSITORY>
cd <YOUR REPOSITORY>
git checkout --orphan gh-pages
# preview the files to be deleted
$ git rm -rf --dry-run .
# delete the files
$ git rm -rf .
Create an index.html file in the root folder:
<!DOCTYPE html>
<html>
<head>
<title>My title</title>
</head>
<body>
<h1>My page</h1>
</body>
</html>
Then commit and push this change:
# should list only index.html
git status
# add, commit and push
git add .
git commit -m "initial commit in gh-pages"
git push --set-upstream origin gh-pages
Head over to github.com/<your account>/<your repository>. You should now have a published gh-pages branch:

Select the branch. It should only contain an index.html file:

Activate GitHub pages for the repository
Open the Settings tab on your repository at github.com. Select Pages and choose to use the gh-pages branch for GitHub Pages (GitHub should do this automatically when you create a branch named gh-pages, but check to be sure anyway):

Note that it may take a few minutes for the site to be published. In the screenshot above, the information text says “…is ready to be published…”. It will change to “…is published…” when finished:

If you browse to your site (in my case https://larsbergqvist.github.io/gh-pages-example), you should be served the index.html file showing “My Page”.
A CI/CD example for an Angular application
As a GitHub Actions+Pages example, let’s create a CI/CD pipeline for an existing Angular application. This section assumes that you have a repository with code for an Angular application available. There should be some tests included and karma.conf.js should be setup so that the test session executes as single run with headless Chrome. You can use this repository as a starting point: https://github.com/LarsBergqvist/gh-actions-example. This repository contains an Angular application with a navigation bar and two routes (/first and /second):

So, the application uses internal routing and we need to support that when hosting it on GitHub Pages. More on this later.
Install dependencies and run tests
The first version of our GitHub Actions-workflow should install npm dependencies and run tests. Create a yml-file, cd.yml in the the folder <root folder>/.github/workflows in your repository. Add this content:
name: Continuous Deployment | |
on: | |
push: | |
branches: ['**'] | |
jobs: | |
yarn-install-and-test: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v2 | |
- name: Setup node | |
uses: actions/setup-node@v2 | |
with: | |
node-version: '14' | |
cache: 'yarn' | |
- name: Install packages | |
run: yarn install | |
- name: Run tests | |
run: yarn test |
The ‘on’-statement defines that this workflow should be triggered when a code change is made to any branch in the repository and the one-and-only job in this workflow should run on the latest version of an ubuntu instance. The job contains steps with these actions:
- uses: actions/checkout@v2 – this makes a checkout of the repository code so that the workflow can access it
- uses: actions/setup-node@v2 – this allows us to use node with package managers (npm or yarn) with caching
- run: yarn install – this executes ‘yarn install’ as a command
- run: yarn test – this executes ‘yarn test’ as a command
Push these changes to GitHub and check on the Actions tab that the workflow is executed properly.
Build the application and upload the artifacts
The next step is to add steps/actions for building the application and uploading the artifacts so that we at a later stage can deploy them.
Modify the cd.yml file so that it looks like this:
name: Continuous Deployment | |
on: | |
push: | |
branches: ['**'] | |
jobs: | |
test-and-build: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v2 | |
- name: Setup node | |
uses: actions/setup-node@v2 | |
with: | |
node-version: '14' | |
cache: 'yarn' | |
- name: Install packages | |
run: yarn install | |
- name: Run tests | |
run: yarn test | |
- name: Build for GitHub pages | |
run: | | |
yarn build-gh-pages | |
cp dist/gh-actions-example/index.html dist/gh-actions-example/404.html | |
- name: Upload artifacts | |
uses: actions/upload-artifact@v2 | |
with: | |
name: build-artifacts | |
path: dist/gh-actions-example |
In package.json, I have a dedicated command for building the application so that it fits GitHub Pages. You need to add a base-href that corresponds to the sub-url that will be targeted in GitHub Pages:
"build-gh-pages": "ng build --configuration production --base-href=/gh-actions-example/"
This builds the application in production mode and sets the base-href to the name of the repository (this is needed because the GitHub Page site has one single url and a separate sub-url for each project deployed to it).
The build is executed as a command ‘run: yarn build-gh-pages’ by the workflow.
After the build, a file copy of index.html to 404.html is made. This is a workaround for making Angular routing work with GitHub Pages. If the browser reloads the application by a refresh or if the route is manually changed in the address bar, you will get a 404/notfound-error. 404.html is a file that GitHub Pages uses when a file can not be loaded. If we let it be a copy of index.html, the routed Angular application will load properly after a refresh/url-change.
At the end there is an ‘Upload artifacts’ step. It will upload the output from the build step to GitHub Actions so that it can be used with a linked job.
If you push these changes to the repository, you should see that a ‘build-artifacts’ is available from the workflow run:

Deploy the artifacts to GitHub Pages
With the build artifacts available, we can now add a new job that deploys the artifacts to GitHub Pages. Add this to the end of cd.yml:
deploy: | |
needs: test-and-build | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v2 | |
- name: Download build artifacts | |
uses: actions/download-artifact@v2 | |
with: | |
name: build-artifacts | |
path: dist/gh-actions-example | |
- name: Deploy to gh-pages branch | |
uses: JamesIves/github-pages-deploy-action@4.1.4 | |
with: | |
branch: gh-pages | |
folder: dist/gh-actions-example |
Workflow jobs run in parallel by default, but as the deploy-job depends on the artifacts from the test-and-build job, it has a ‘needs’-statement defined. The steps for the deploy job downloads the build artifacts and then uses the github-pages-deploy-action for deploying the artifacts to the gh-pages branch of the repository.
After pushing this change, the workflow starts and runs two jobs in sequence as a pipeline.

When finished, a gh-pages branch that contains the built Angular application files should have been created:

and the application should automatically be published to GitHub Pages (it might take a couple of minutes to finish when then the gh-pages branch is created for the first time). In my case the application is published to https://larsbergqvist.github.io/gh-actions-example/
A continuous deployment example for a React application
One of my old pet projects is a memory card game using React & Redux: https://github.com/LarsBergqvist/react-redux-memory-game
It used a travis build pipeline previously, but as I jumped off the travis-train, it had to be migrated to GitHub Actions. The old travis-file looked like this:
dist: xenial | |
sudo: false | |
language: node_js | |
node_js: | |
- "10" | |
cache: | |
directories: | |
- ./node_modules | |
install: | |
- yarn install | |
script: | |
- yarn test | |
- yarn build | |
deploy: | |
provider: pages | |
skip_cleanup: true | |
github_token: $GITHUB_TOKEN | |
local_dir: build | |
on: | |
branch: master |
With Travis, you need to provide a generated GITHUB_TOKEN as a secret in the build so that Travis is allowed to make changes to your repository. With GitHub Actions you get automatic access to the repository. My new GitHub Actions workflow file looks like this:
name: Continuous Deployment | |
on: | |
push: | |
branches: [master] | |
jobs: | |
test-build-and-deploy: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v2 | |
- name: Setup node | |
uses: actions/setup-node@v2 | |
with: | |
node-version: '14' | |
- name: Install packages | |
run: yarn install | |
- name: Run tests | |
run: yarn test | |
- name: Build for GitHub pages | |
run: yarn build | |
- name: Deploy to gh-pages branch | |
uses: JamesIves/github-pages-deploy-action@4.1.4 | |
with: | |
branch: gh-pages | |
folder: build |
This application does not use routing and I have chosen to do test, build and deploy in within the same job. Thus, the workflow configuration is more simple than the Angular example.
Wrap-up
GitHub Actions fits my needs for continuous integration and deployment of open-source applications well. It is free and as my repositories are in GitHub it feels natural to let GitHub handle the CI/CD pipelines as well.
GitHub Pages is very useful for demo purposes. Hosting an application that uses internal routing is a bit tricky but manageable.