A question was raised in a forum asking how to deploy static websites for free. Then, more specifically, it was mentioned they were using Nx to generate multiple websites. In this article, we will create a new Nx project with a few apps, publish our code to github, deploy all the websites to GitHub actions, and set up Cloudflare to share our custom domain. Prerequisites: git and nodejs installed.
Table of Contents
Setup a New Nx Project
That’s easy. We can use npx create-nx-workspace
. We answer some setup questions. Here are my answers:
For this case, the most important answer is that we’d like an integrated monorepo. This allows us to create multiple projects and deploy them easily.
Once this is setup, we cd
into our project (in my case, it is cd y-static-websites
). Inside, we can install our wanted packages. Let’s create three projects: vue, angular and web.
Install the Web, Vue and Angular Packages
npm i -D @nx/angular @nx/vue @nx/web
Create the projects
npx nx g @nx/web:project apps/app1
npx nx g @nx/vue:project apps/app2
npx nx g @nx/angular:project apps/app3
The above commands will generate 3 projects for us inside the apps
folder. For the first two I opted for vite
as the builder. For the angular project, I’ve selected esbuild
as the builder.
Each app can be developed using npx nx run {appName}:serve
(e.g. nx run app1:serve
which loads a dev server for us. But right now, we are focused on deploying our websites.
Configure the Builders to Serve the Applications
Because we are going to serve all websites from the same top domain, we need to tell the browser where to look for the files. In app1
and app2
, we can just add the base
property to vite.config.ts
:
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/app1',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
base: '/app1/',
build: {
outDir: '../../dist/apps/app1',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});
Note the base: '/app1/'
property added to the config. We do the same for app2
(only with base: '/app2/'
).
For the angular app, we add baseHref
to the build
configuration in project.json
:
"targets": {
"build": {
"baseHref": "/app3/browser/",
... rest of config goes here
Notice that the angular build is set inside the app3/browser
folder.
Now we are ready to test if this works.
Build the Apps
We build the apps in a single command:
npx nx run-many --target=build --all
and dist
is generated for us.
Test the Apps Locally
In order to test that the apps load locally, let’s cd
into the main deploy folder: cd dist/apps
.
Once there, we can setup a local static server like this: npx static-server
. This loads a simple server that works kind of like github pages
.
It will output the URL where the server is served (default is http://localhost:9080). Browse the URL and append the application’s folder. So app1
will be: http://localhost:9080/app1
, and app3
will be http://localhost:9080/app3/browser
.
If everything works fine, all 3 websites should work.
Load the Code to Github
Loading the code to github requires to create a new github repository.
Create a new github repository (or browser here). Once done, follow the instructions to push an existing repository from the command line:
git remote add origin https://github.com/YonatanKra/my-static-websites.git
git branch -M main
git push -u origin main
Don’t forget to replace the origin
URL with yours. And, of course, you should run it from the project’s root (just in case you are still in dist/apps
š ).
How to Deploy Apps to Github Pages Using Github Actions?
Now our repository is on Github! Hooray. We want to activate Github pages with Github actions.
We can set it up in the repository’s settings (orange arrow in Figure 2).
Once inside Settings
, go to Pages
(Yellow arrow). Open the select box (Purple arrow) and make sure to select Github Actions
. This will display the option to configure Static HTML deployment (White arrow).
After hitting Configure
a new screen will open with a workflow file ready to commit. It looks like this:
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content 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:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
The important part is the on
section and the steps
phase.
on
tells github actions when to run. In our case, on every push to the main
branch.
steps
is what is actually being done in the deploy
job.
It currently checks out our code from the repository (the Checkout
step), setup github pages
, uploads some artifacts (currently the whole project), and finally deploys to GitHub pages.
What we’d like to do is the following:
- Checkout
- Npm install
- Build
- Configure pages
- Upload
dist/apps
as artifacts - Deploy to github pages
So our code will look like this:
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content 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:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Npm install
run: npm ci
- name: Build
run: npx nx run-many --target=build --all
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload apps dist folder
path: './dist/apps'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Notice the steps added Npm install
, Build
and the change to upload artifact
to upload only the dist/apps
folder.
Here’s the repository: https://github.com/YonatanKra/my-static-websites
Our websites won’t show now. We need to set up our custom domain in Cloudflare.
Setup Our Domain in Github
Let’s assume we have the domain yonatankra.com
. We’d like to set up a subdomain my-static-websites.yonatankra.com to redirect to our GitHub pages static websites.
First, let’s set it up in the Github repository’s settings. Go back to Settings => Pages and find the Custom domain
input box. Write down your custom domain (in my case my-static-websites.yonatankra.com
, but in your case, it should probably be different).
After you save the domain, it will try to verify its settings and will probably fail. That’s because we need to change the domain’s settings in Cloudflare.
Setup Our Domain in Cloudflare
This step assumes you already have a Cloudflare account. If you do not, create one.
There are several steps to set up your domain in Cloudflare:
- Create a new website (purple arrow in Figure 3)
- Either transfer your domain or register a new one in Cloudflare
- Choose the Free program
- Follow the instructions to set up your domain on Cloudflare
Once the domain is set up, you should be able to add DNS records (see DMS Records screen in Figure 4).
On this page, add 3 new A records, as shown in Figure 5. Note that you need to add the 3 records because Github pages work with 3 IP addresses:
185.199.109.153
185.199.110.153
185.199.111.153
Once that’s done, your domain will direct to your GitHub pages. If you go to the URL my-static-websites.myDomain.com, you will get an infinite redirect error. That’s because we need to define how SSL is handled in Cloudflare. By default, the SSL is set to Flexible. Github pages do not support it, so you need to define it to Full for this subdomain.
Under the SSL tab (figure 6) you will have a link to Configuration Rule
creation. Click it.
You will be redirected to the Configuration Rules page (figure 7).
Click on Create rule
. Set a rule name
and set the condition as shown in Figure 8 (remember to change the domain name). This will ensure that this rule applies only to this subdomain.
If you create more Github actions in the future, you can add more incoming request matches. Now, we need to define the rule. Scroll down until you see the SSL box (Figure 9).
After you have made sure the encryption mode is full, click on Deploy
.
Now you can browse the 3 static websites and do whatever you want with them:
https://my-static-websites.yonatankra.com/app1
https://my-static-websites.yonatankra.com/app
2
https://my-static-websites.yonatankra.com/app3/browser
Summary
This guide might seem a bit long but it shouldn’t take more than 5 minutes after the first initial setup.
Of course, you can optimize the GitHub actions flow, but that’s a topic from a different article.