For one of my projects I have a few different site developers and content authors. I really wanted to simplify the process of adding a new member to the team and get them up to speed quickly.

Fortunately, the Static Site Generator “Hugo” uses compiled binaries and does not require anything setup on the system to run. Having said that, setting up a couple of external tools (NPM and Python) can make running and updating Hugo on any OS a breeze for anyone on the team.

First, I store the binaries in a folder named bin in the root of my project.

You will notice that the file names are a bit different than the default ‘hugo’ name. This is to help differentiate them in the directory. I am using the ‘extended’ version of Hugo for the awesome ‘Pipes’ feature. I use a Python script to pull down the most recent version and create those names based on information in the release filename. The only parameter to set in the script before running it, is the version that you want to install.

import urllib.request
import tarfile
import zipfile
import os
import shutil

print('Updating Hugo binaries')

## Need to set the version number.
version = "0.46"
tarNames = ["macOS","Linux"]
zipNames = ["Windows"]
urlBase = "https://github.com/gohugoio/hugo/releases/download/v{}/".format(version)

for i in range(len(tarNames)):
  tarFilename = "hugo_extended_{}_{}-64bit.tar.gz".format(version,tarNames[i])
  print("Processing: "+tarFilename)
  urllib.request.urlretrieve(urlBase+tarFilename, tarFilename)
  tar = tarfile.open(tarFilename, "r:gz")
  tar.extractall("temp")
  tar.close()
  shutil.copyfile("./temp/hugo", "hugo_extended.{}".format(tarNames[i].lower()))
  shutil.rmtree("temp")
  os.remove(tarFilename)
  i += 1

for i in range(len(zipNames)):
  zipFilename = "hugo_extended_{}_{}-64bit.zip".format(version,zipNames[i])
  print("Processing: "+zipFilename)
  urllib.request.urlretrieve(urlBase+zipFilename, zipFilename)
  with zipfile.ZipFile(zipFilename,"r") as zip_ref:
    zip_ref.extractall("temp")
  shutil.copyfile("./temp/hugo.exe", "hugo_extended-{}.exe".format(zipNames[i].lower()))
  shutil.rmtree("temp")
  os.remove(zipFilename)
  i += 1

print("Hugo has been updated to version {}.".format(version))

After that script runs, you have these binaries that need to be run. How do I run them you ask? Through a collection of npm scripts of course! Below is the scripts section of my package.json file.

"scripts": {
    "clean_public_win": "if exist \"public\\\" rmdir public /S /Q",
    "clean_resources_win": "if exist \"resources\\\" rmdir resources /S /Q",
    "clean_win": "npm run clean_public_win && npm run clean_resources_win",
    "build_win": "npm run clean_public_win && npm run clean_resources_win && .\\bin\\hugo_extended-windows.exe --gc",
    "serve_win": "npm run clean_resources_win && .\\bin\\hugo_extended-windows.exe server --disableFastRender",
    "clean_osx": "rm -rf public && rm -rf resources",
    "build_osx": "chmod a+x ./bin/hugo_extended.macos && rm -rf public && rm -rf resources && ./bin/hugo_extended.macos --gc",
    "serve_osx": "chmod a+x ./bin/hugo_extended.macos && rm -rf resources && ./bin/hugo_extended.macos server --disableFastRender",
    "clean_lin": "rm -rf public && rm -rf resources",
    "build_lin": "chmod a+x ./bin/hugo_extended.linux && rm -rf public && rm -rf resources && ./bin/hugo_extended.linux --gc",
    "serve_lin": "chmod a+x ./bin/hugo_extended.linux && rm -rf resources && ./bin/hugo_extended.linux server --disableFastRender"
  },

Now a developer or content creator working locally only has to run the following commands to get setup after they clone the repository to their computer (regardless of OS).

npm install, then npm run [action]_[os reference]

Where [action] is one of 3 options:

  • clean - removes the public and resources folders from their project directory.
  • build - removes the public and resources folders and then runs Hugo with garbage collection turned on to build the site. This is done before files are commited to the repository to make sure the Hugo Pipes are run and all unused resources are removed before commit.
  • serve - removes the public and resources folders and then runs the local Hugo server feature, with the --disableFastRender flag. I like this flag, because for my site Hugo builds the entire thing fast enough and it ensures that all content is updated in the preview site being served.

Where [os reference] is one of 3 options:

  • win - for Windows OS users.
  • osx - for Mac OSX users.
  • lin - for Linux OS users.

I have Windows on my desktop, so I run the command npm run serve_win to start up the server and get to work. Then before I commit any changes to the repository. I press CTRL-c & Y to stop the server and then enter npm run build_win to prep the site as it needs to be before pushing anything to the repository.

I use Netlify to build my site, and I only give it the hugo build command and set the version of Hugo to use in the Netlify environment variables. At the time of this post, the ‘extended’ version of Hugo does not run on Netlify, due to some Linux library errors. Once this is resolved, I will not need to run the build command to generate the resources that I commit to the repository. That will be nice and simplify the workflow a bit more, but the current process is painless.

So far, this setup has made my life easier and more importantly it has impressed a long time Jekyll dev/user working with me on a project, who is now considering switching their Static Site Generator of choice to Hugo and using a setup like this for her own projects. :D

There are some other things that I have setup to make VS Code easier to use in this configuration and with Hugo, but that is for another post.