Introduction

The way I produce content tends to be rather fractured. I’m often working on 4 or 5 articles at a time, content may need further research or contain images, code, links that require multiple revisions before I consider them fit for public consumption. Not to mention numerous proofreads for the inevitable grammar, spelling mistakes and typos.

For these reasons (and more) I prefer to maintain my content directory as a separate private submodule. Tania Rascia wrote an excellent tutorial on how to implement this.

Recently I found time to try out Vercel. Vercel’s slogan ‘Develop. Preview. Ship.’ is a succinct summary of what the platform enables developers to achieve. Tight integration with GitHub, GitLab or Bitbucket plus a plethora of features help streamline your development process.

However, while experimenting with the platform I discovered that private submodules are currently unsupported. See below from their site:

Deploying Git submodules with a Git provider is supported as long as the submodule is publicly accessible via the HTTP protocol. Git submodules that are private or requested over SSH will fail during the Build step.

Source: Vercel - Git Submodules

Support for private submodules is forthcoming. In the mean time however I needed a way to deploy my (or any Hugo) site to Vercel while maintaining a private submodule for the content.

This is where GitHub Actions comes in. GitHub Actions automates your development workflow. Simply put GitHub Actions combines all the manual steps you would take to achieve your goal and automates them. In this case, the deployment of a Hugo site on Vercel’s platform.

This tutorial is a step-by-step guide on how to setup a simple GitHub Actions workflow that accommodates private submodules:

  1. Develop your Hugo site locally
  2. Push commits to a develop branch on GitHub
  3. GitHub Actions builds your site (with private submodule) and deploys it to Vercel
  4. Vercel returns a preview URL where you (or your team) can review your site
  5. If satisfied, you can create a pull request to merge the develop branch into a main branch
  6. Upon merge confirmation GitHub Actions builds your site again and deploys it to Vercel for production
  7. Vercel returns a production URL where you can view your live site

An additional benefit of using GitHub Actions is that your workflow is no longer restricted to features available only on Vercel’s platform. The GitHub Actions community is extensive, granting you the flexibility to adapt any workflow to your specific needs far beyond the scope of simple access to private submodules.

Prerequisites

  • Vercel Account (sign up)
  • Hugo & Vercel CLI installed locally
  • Basic knowledge of Git & GitHub

Step 1 - Grant GitHub Actions Access to your Private Submodule

In order for GitHub Actions to successfully fetch your private submodule it needs to be granted access. This can be accomplished by configuring a Deploy key in the GitHub repository of the private submodule.

First, generate a new key locally using the ssh-keygen command:

ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/submodule_access_deploy_key_ed25519

DO NOT add a passphrase. Leaving the passphrase empty, press return twice.

COMMAND OUTPUT - (click to expand)
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ~/.ssh/submodule_access_deploy_key_ed25519
Your public key has been saved in ~/.ssh/submodule_access_deploy_key_ed25519.pub
The key fingerprint is:
SHA256:rWi6yKn0dyB5kZZWnTyye1kVvtcAP/ZZPyEfI0p8DYg my@DESKTOP-1FTAJ7O
The key's randomart image is:
+--[ED25519 256]--+
|        o o oo.  |
|       o E...+o  |
|      + o .o.===.|
|     * . ...o.=+O|
|    + . S +. . =+|
|   o o o +    . .|
| .  o + o        |
|...o + .         |
|..+.+..          |
+----[SHA256]-----+

The public part of the key pair (submodule_access_deploy_key_ed25519.pub) can now be uploaded to the submodule repo.

Navigate to your submodule repo on GitHub, Settings > Deploy Keys and click on Add deploy key. Choose a title for your new key and enter the public key value:

Leave the Allow write access checkbox UNCHECKED!

Click on Add key to complete the process.

Now navigate to your main/parent repo. Click Settings > Secrets > New repository secret. Choose a name for your secret/private key and enter the private key value:

Click on Add secret to finish.

Step 2 - Install Vercel CLI & Create a new Project

GitHub Actions also needs access to your Vercel account in order to push your Hugo site from GitHub to their platform. Three values are required for this.

Generate a Vercel Account Token

Visit https://vercel.com/account/tokens and click on createto generate a new token (save this token for later):


Obtain your Vercel Organisation & Project ID

To obtain a Vercel Organisation & Project ID a new project needs to be created. For this we’ll use the Vercel CLI. Use either the npm or yarn package manager to install the Vercel CLI in your local development environment:

npm
npm i -g vercel

yarn
yarn global add vercel

Now switch to the root of your local Hugo project before using the vercel command.

Your local file structure should look similar to this
my_hugo_site/
|-- .gitignore
|-- .gitmodules
|-- archetypes/
|-- config.toml
|-- content/
|   |-- .git
|   `-- posts/
|-- data/
|-- layouts/
|-- public/
|-- resources/
|-- static/
`-- themes/

The my_hugo_site directory corresponds to your public parent repo on GitHub while the content directory to your private submodule repo.


Login to Vercel

Login to Vercel by running vercel or vercel login at the root of your project. The CLI will send and email to the address registered with your Vercel account. Click on the link in the email to complete login process:

/my_hugo_site$ vercel
Vercel CLI 21.0.1
> No existing credentials found. Please log in:
We sent an email to myemail@example.com. Please follow the steps provided inside it and make sure the security code matches Sunny Moth.
โˆš Email confirmed
Congratulations! You are now logged in. In order to deploy something, run `vercel`.
๐Ÿ’ก  Connect your Git Repositories to deploy every branch push automatically (https://vercel.link/git).

Deploy your site to create a new project on Vercel

Now that you’re logged in run the vercel command again to configure and deploy your site to Vercel as a new project:

/my_hugo_site$ vercel
Vercel CLI 21.0.1
? Set up and deploy โ€œ~/my-hugo-siteโ€? [Y/n] y
? Which scope do you want to deploy to? vercelusername
? Link to existing project? [y/N] n
? Whatโ€™s your projectโ€™s name? my-hugo-site
? In which directory is your code located? ./
Auto-detected Project Settings (Hugo):
- Build Command: `npm run build` or `hugo -D --gc`
- Output Directory: `public` or `publishDir` from the `config` file
- Development Command: hugo server -D -w -p $PORT
? Want to override the settings? [y/N] n
๐Ÿ”—  Linked to vercelusername/my-hugo-site (created .vercel and added it to .gitignore)
๐Ÿ”  Inspect: https://vercel.com/vercelusername/my-hugo-site/1rx3k75a7 [2s]
โœ…  Production: https://my-hugo-site.vercel.app [copied to clipboard] [11s]
๐Ÿ“  Deployed to production. Run `vercel --prod` to overwrite later (https://vercel.link/2F).
๐Ÿ’ก  To change the domain or build command, go to https://vercel.com/vercelusername//my-hugo-site/settings

You can accept all the default options presented since the main purpose of running the vercel command is to obtain your organisation and project IDs.

Once deployed search the newly generated local .vercel directory for the project.json file, it’ll contain your new organisation and project IDs:

{"orgId":"obn4rXWTQ7oc9FjAG9g5klo5","projectId":"prj_3vDZFk6xG1E5ACWMd0nxA264OQVR"}

Return to your main (Hugo) site repository on GitHub and add these three values (Vercel Token, Organisation ID and Project ID) as appropriately named secrets alongside your existing SUBMODULE_CONTENT_DEPLY_KEY secret.

Step 3 - Update Vercel Project Settings

IMPORTANT: Since GitHub Actions will be responsible for building your Hugo site your new project settings need be updated so that Vercel doesn’t attempt to build your site again once GitHub Actions has pushed your content to Vercel.

In the Vercel dashboard navigate to the Settings of your new project. Click the switch to override the BUILD COMMAND. Leave the input field empty. And save your settings:

Step 4 - Create the GitHub Actions Workflow Files

Now that your submodule and parent repositories both have the necessary secrets configured we can proceed to creating the GitHub Action workflow(s) that will deploy your site on Vercel.

If you haven’t already, Create a new (develop) branch from your main/master branch and add the following workflow file(s):

deploy-preview.yaml (click-to-expand)
# deploy-preview.yaml

# Workflow to build and deploy site to Vercel using Hugo
# Name of Workflow
name: deploy-preview

# Controls when the action will run. Triggers the workflow on push
# events but only for the develop branch
on:
  push:
    branches: [ develop ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "deploy-preview"
  deploy-preview:
    # The type of runner that the job will run on
    runs-on: ubuntu-20.04

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:

    # Step 1 - Checks-out your repository under $GITHUB_WORKSPACE
    - name: Checkout
      uses: actions/checkout@v2
      with:
          ssh-key: ${{ secrets.SUBMODULE_CONTENT_DEPLOY_KEY }}
          submodules: recursive  # Fetch private content

    # Initiate deployment status
    - name: Start Deployment
      uses: bobheadxi/deployments@v0.4.3
      id: deployment
      with:
        step: start
        token: ${{ secrets.GITHUB_TOKEN }}
        env: Preview

    # Step 2 - Install Hugo (specific version)
    - name: Install Hugo
      uses: peaceiris/actions-hugo@v2
      with:
          hugo-version: '0.79.1'

    # Step 3 - Builds the site using Hugo
    - name: Build
      run: hugo -v

    # Step 4 - Push our generated site to Vercel
    - name: Deploy to Vercel
      uses: amondnet/vercel-action@v20
      id: vercel-action
      with:
        vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
        #github-token: ${{ secrets.GITHUB_TOKEN }} #Optional
        vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}  #Required
        vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} #Required
        github-comment: false
        #vercel-args: '--prod' #Optional
        working-directory: public

    # Update deployment status
    - name: Update Deployment Status
      uses: bobheadxi/deployments@v0.4.3
      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 }}

deploy-production.yaml (click-to-expand)
# deploy-production.yaml

# Workflow to build and deploy site to Vercel using Hugo
# Name of Workflow
name: deploy-production

# Controls when the action will run. Triggers the workflow on push
# events but only for the main/master branch
on:
  push:
    branches: [ main ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "deploy-production"
  deploy-production:
    # The type of runner that the job will run on
    runs-on: ubuntu-20.04

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:

    # Step 1 - Checks-out your repository under $GITHUB_WORKSPACE
    - name: Checkout
      uses: actions/checkout@v2
      with:
          ssh-key: ${{ secrets.SUBMODULE_CONTENT_DEPLOY_KEY }}
          submodules: recursive  # Fetch private content

    # Initiate deployment status
    - name: Start Deployment
      uses: bobheadxi/deployments@v0.4.3
      id: deployment
      with:
        step: start
        token: ${{ secrets.GITHUB_TOKEN }}
        env: Production

    # Step 2 - Install Hugo (specific version)
    - name: Install Hugo
      uses: peaceiris/actions-hugo@v2
      with:
          hugo-version: '0.79.1'

    # Step 3 - Builds the site using Hugo
    - name: Build
      run: hugo -v --minify

    # Step 4 - Push our generated Hugo site to Vercel
    - name: Deploy to Vercel
      uses: amondnet/vercel-action@v20
      id: vercel-action
      with:
        vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
        #github-token: ${{ secrets.GITHUB_TOKEN }} #Optional
        vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}  #Required
        vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} #Required
        github-comment: false
        vercel-args: '--prod'
        working-directory: public

    # Update deployment status
    - name: Update Deployment Status
      uses: bobheadxi/deployments@v0.4.3
      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 }}

in a .github/workflows directory at the root of your project:

my_hugo_site/
|-- .github/
|   `-- workflows/
|       |-- deploy-preview.yaml
|       `-- deploy-production.yaml
|-- |

The above workflows are well commented but here are a few additional details on some of the actions used:

  • Line 11: The workflow is triggered when code is pushed to the defined branch. This includes when a pull request is merged into the branch. e.g. main <– develop triggers the push event on the main branch.

  • Line 25, 27: Using your SUBMODULE_CONTENT_DEPLOY_KEY secret the actions/checkout action checks out your Hugo and private submodule repository.

  • Line 41: The peaceiris/actions-hugo installs Hugo on the virtual machine.

  • Line 51, 54-57: The amondnet/vercel-action action uses your VERCEL_TOKEN, VERCEL_ORG_ID and VERCEL_PROJECT_ID secrets to authenticate with and push your Hugo site to the Vercel platform. It returns a preview URL generated by Vercel where you can view your site live.

  • Line 32, 70, 71: The bobheadxi/deployments action uses the URL returned by the amondnet/vercel-action to create and update a Deployment and Deployment status before, during and after your workflows’ build and push tasks. You can view the status for each deployment by visiting https://github.com/<your_github_username>/<your_hugo_site_repo>/deployments:

Clicking View deployment for either environment will take you to the Preview or Production deployment on Vercel

Step 5 - Publish your ‘develop’ Branch to GitHub

You’ve created a new project on Vercel and your GitHub actions workflow files are done so it’s time to push/publish your develop branch to GitHub. Once you do, GitHub actions will queue your deploy-preview.yaml workflow for execution. Go to your site repo on GitHub and click on Actions. Click the listed workflow to view the results. Below is an example summary of a successfully executed workflow:

vercel-dev is the name of my ‘develop’ branch. Also shown are the Status (success) and the duration the action took to run.

Clicking on deploy-preview under Jobs will show a summary of the steps in the workflow. Click on a step to view it in more detail:

As mentioned in the previous step, navigate to https://github.com/<your_github_username>/<your_hugo_site_repo>/deployments and click on View deployment to see a live preview of your site hosted on Vercel.

Step 6 - Deploy your Hugo Website

If after reviewing the Vercel preview of your develop branch you wish to go ahead and deploy to production, simply merge your develop branch into your main branch via a pull request:

  1. Merge vercel-dev into vercel (the respective names of my ‘develop’ and ‘main’ branches)
  2. The commits that will be merged
  3. Link to the live Vercel preview deployment of the changes that are to be merged
  4. Click for details of the workflow

Once the pull request is merged the deploy-production.yaml workflow will trigger building and deploying your live production site in the same way your preview deployment was ๐Ÿš€.

By default, all deployments on the Vercel platform are assigned a .vercel.app suffixed domain. If you haven’t already you can add a custom domain to your production deployment.

Summary

You’ve now setup a simple workflow that will accommodate private submodules:

  • Make local changes to your Hugo site
  • Commit & Push those changes to a remote ‘develop’ branch
  • GitHub Actions builds and deploys your site to Vercel
  • Review the Preview of your site on Vercel
  • Create a Pull Request to merge your changes from your develop to main branch
  • GitHub Actions again builds and deploys your site to Vercel
  • View your site in production on Vercel

Keep in mind that this tutorial covers only the basics of what can be achieved through GitHub Actions. All sorts of additional tests can be integrated with your workflows such as LightHouse audits of your Hugo URLs and Visual Testing via Percy. The possibilities are almost limitless.

References