21
Running Tighten's Jigsaw on Vercel
For more than two years, my website was hosted on Netlify. This summer, the poor loading performance got on my nerves, and I decided to migrate the site to Vercel. The results are stunning! The average response time dropped from ~500ms to ~100ms.
My site is built with Jigsaw, a PHP static site builder.
That is the first problem: PHP. You can't run a PHP binary in Vercel's build process. The community PHP runtime can't be used either: I need to run a PHP CLI; not deploy a PHP lambda function.
So how did I deploy this site to Vercel? If you know me well, you know the answer already: GitHub Actions.
I've created a GitHub Actions workflow that checks out my repository, installs the composer and NPM dependencies, builds the site, tests the site for common errors and then – finally – uploads the site to Vercel's edge network.
Before we begin: this setup looks intimidating. If you would like to host your Jigsaw websites in a simpler way, I can recommend following Michael Dyrynda's blog post on how to deploy a Jigsaw site to Netlify.
GitHub Actions workflow to deploy the site#
Before switching to Vercel as my website host, I already used GitHub Actions to create a new build of the website on every commit push. The workflow acted like a test runner. It ensured, that my changes – or dependeny updates created by Dependabot – didn't break the site.
All I had to add was a new job that uploaded the prepared website to Vercel. Below is the stripped down workflow annotated with comments, to explain what each step does.
I've removed a couple of steps that do the quality control (look for weasel words, validate RSS feed, etc.) for me. I will share more about these steps in the future.
To use the workflow you will need to create a couple of secrets in your repository. You will need a VERCEL_TOKEN
, VERCEL_PROJECT_ID
and VERCEL_ORG_ID
.
# integrate.yml
name: Integrate
# Run workflow when commits are pushed to pull requests or main branch
on:
pull_request: null
push:
branches:
- main
jobs:
# Build Job
# This job install necessary dependencies, generate a new production build
# of the website and make the build available to other jobs in the workflow.
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Restore node_modules folder
id: cache-node
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-node-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-v2
- name: Install frontend dependencies
if: steps.cache-node.outputs.cache-hit != 'true'
run: yarn install
env:
CI: true
- name: Create production build of CSS and JS
run: yarn run prod
- name: Restore Composer dependencies
id: cache-php
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
# If no Composer cache exists, install Composer dependencies
- name: Install Composer dependencies
if: steps.cache-php.outputs.cache-hit != 'true'
run: composer install -n --ignore-platform-reqs
- name: Create new production build of website
run: composer run build:prod
- name: Create ZIP file of production build
run: zip -r build_production.zip build_production/
- name: Keep ZIP file as artifact
uses: actions/upload-artifact@v2
with:
name: build_production_zip
path: build_production.zip
retention-days: 1
# Deploy Preview Job
# This job will deploy the website to a preview domain and is only executed
# when a commit has been pushed to a pull request and the author of the pull
# request is not Dependabot.
deploy_preview:
name: Deployment Preview
if: github.event_name == 'pull_request' && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
# The job is only run, when the `build`-job is finished.
needs:
- build
steps:
- uses: actions/checkout@v2
- name: Download build artifact
uses: actions/download-artifact@v2
with:
name: build_production_zip
path: ./build
- name: Unzip production build
run: unzip ./build/build_production.zip -d ./build_production
# Start tracking the deployment status using GitHub deployments
- name: Start deployment
uses: bobheadxi/deployments@master
id: deployment_pr
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: "Pull Request #${{ github.event.number }} Preview"
# `head_ref` has to be used here, as otherwhise the
# deployments are not shown near the status overview inside
# a pull request
ref: ${{ github.head_ref }}
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 16
- name: Restore node_modules folder
id: cache-node
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-node-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-v2
- name: Install frontend dependencies
if: steps.cache-node.outputs.cache-hit != 'true'
run: yarn install
env:
CI: true
# Deploy website as a preview to Vercel.
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
id: vercel_action_pr
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
# Disable GitHub comments. I don't need a comment telling me
# that a deployment is happening.
github-comment: false
# This is not a typo. This structure is created by unzipping
# the production build artifact.
working-directory: ./build_production/build_production
# Set the deployment status in GitHub to finished.
- name: Update Deployment Status
uses: bobheadxi/deployments@master
if: always()
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
# We use the deployment ID of a previous step here
deployment_id: ${{ steps.deployment_pr.outputs.deployment_id }}
# We pass Vercel's own preview URL to the environment. This
# way we can easily visit the deployed site from the pull
# request.
env_url: ${{ steps.vercel_action_pr.outputs.preview-url }}
- name: Delete production build artifact
uses: geekyeggo/delete-artifact@v1
if: always()
with:
name: build_production_zip
# Deploy Production Job
# This job will deploy the website to production and will only be executed
# when a commit is pushed to the default `main`-branch.
deploy_prod:
name: Deployment
if: github.event.ref == 'refs/heads/main'
runs-on: ubuntu-latest
# The job is only run, when the `build`-job is finished.
needs:
- build
steps:
- uses: actions/checkout@v2
- name: Download build artifact
uses: actions/download-artifact@v2
with:
name: build_production_zip
path: ./build
- name: Unzip production build
run: unzip ./build/build_production.zip -d ./build_production
# Start tracking the deployment status using GitHub deployments.
# Instead of using a dynamic environment name, we use production
- name: Start Deployment
uses: bobheadxi/deployments@master
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: production
- uses: actions/setup-node@v2
with:
node-version: 16
- name: Restore node_modules folder
id: cache-node
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-node-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-v2
- name: Install frontend dependencies
if: steps.cache-node.outputs.cache-hit != 'true'
run: yarn install
env:
CI: true
# Deploy website for production to Vercel.
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
id: vercel-action
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
github-comment: false
working-directory: ./build_production/build_production
# This argument tells the Vercel Action to mark the deployment
# for production
vercel-args: '--prod'
# Set the deployment status in GitHub to finished.
- name: Update Deployment Status
uses: bobheadxi/deployments@master
if: always()
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
env_url: ${{ steps.vercel-action.outputs.preview-url }}
- name: Delete production build artifact
uses: geekyeggo/delete-artifact@v1
if: always()
with:
name: build_production_zip
Add the root of my project, I have the following vercel.json
file.
// vercel.json
{
"github": {
"enabled": false,
"silent": true
}
}
By setting github.enabled
to false
, I tell Vercel to not automatically deploy my site on every push through Vercel. GitHub Actions does that for me. By setting github.silent
to true
, Vercel will stop commenting on commits and pull requests.
Deactivate Environments when closing Pull Requests#
The big workflow above creates a new GitHub environment whenever a pull request is being deployed.
These environments will stay arround forever, if we don't deactivate them.
If you would like to keep things tidy, you can add this additional workflow to your repository. It will be triggered when a pull request is closed or merged and will delete the environment associated with it. (If you have updated the environment name in the workflow above, you have to update this workflow as well.)
# prune-environments.yml
on:
pull_request:
types: [closed]
jobs:
prune:
runs-on: ubuntu-latest
steps:
- name: Deactivate GitHub environment
uses: bobheadxi/[email protected]
with:
step: deactivate-env
token: ${{ secrets.GITHUB_TOKEN }}
env: "Pull Request #${{ github.event.number }} Preview"
desc: Deployment was pruned
Wrap up#
It took a couple of tries to get the workflow to behave as expected; but I really like the end result. I'm now much more in control how the website is built and have access to a more frequently updated Linux machine (another reason I was looking for a different host, as the Netlify base image was stuck on PHP 7.4).
The best thing: The workflow can be applied to any other language.
A framework you like is not compatible with Vercel? Use the workflow above and adjust it so that the build step uses the language or tool of your choosing.
Another cool benefit of having everything in GitHub Actions? I can just chain other Actions to this workflow.
For example, after each deploy I automatically run an Oh Dear check to check if any link on my website is broken.
I hope this article inspired you to give Vercel and GitHub Actions a try. If you have any questions regarding the workflow, let me know!
21