(Updated: )
/ #deployment #meta #css 

Switching the lights on: Hugo vs Hugo config files

A simple Hugo blog setup

The story of getting codewithhugo.com up and running.

The tl;dr is the following:

  • I always rave about my blog setup, it’s simple, fast and just works
  • I used Hugo, “The world’s fastest framework for building websites”, a static site generator
  • I used a theme, casper-two, which is a Hugo port of the https://ghost.org/ default theme
  • I deployed to GitHub pages behind Cloudflare *.

There’s been an update in the deployment saga, read it here: “A tiny case study about migrating to Netlify when disaster strikes at GitHub, featuring Cloudflare”

Table of Contents

This was sent out on the Code with Hugo newsletter last Monday. Subscribe to get the latest posts right in your inbox (before anyone else).

Why I didn’t build my own website 🏃‍♂️

I’m not a designer, and Code with Hugo wasn’t about me learning or showing off my website-making skills, it was about writing content consistently. That’s why I used a template, Hugo and GitHub pages. It’s a static site with a CDN (Cloudflare) in front so pretty much nothing can go wrong. That means I have to focus on the content.

Picking a theme and overriding little things 🖼

Casper Two, the ghost.org default theme is awesome. I tweaked a couple of things, as you can see in my static/overrides.css:

.site-title {
    font-weight: 200;
    font-size: 8rem;
.collection-title {
    text-transform: uppercase;
    font-size: 6rem;
    font-weight: 300;
.site-header:before {
    background: rgba(0, 0, 0, 0.5);
// This was only needed when I enabled pygments for syntax highlighting
.site-wrapper {
    min-height: auto;
.highlight {
    width: 100%;

Adding that stuff in config.toml:

    customCSS = "overrides.css"

That’s all the CSS I wrote 🙂 .

Enabling syntax highlighting 🎨

codewithhugo.com didn’t have syntax highlighting for a while, 😕 and I even started whining.

When @ThePracticalDev has better syntax highlighting than your blog 😂😂 does anyone  know how to sort out @GoHugoIO syntax highlighting https://t.co/PBJAolNlDY pic.twitter.com/2PXyY7l0dV— Hugo Di Francesco (@hugo__df) 13 April 2018

I tried a couple of times to enable it, it was meant to be as simple as putting the following in config.toml:


This stumped me for months, finally it turns out I was doing this


Classic case of blinders being on. As part of this process I got highlight.js working, but in keeping with the “It’s a static site so pretty much nothing can go wrong” it felt a bit wrong to use a client-side library to highlight stuff. Since syntax highlighting is done at build-time, no highlight.js JS in layouts/partials/js.html :

<!-- <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script> -->

If you want it to actually look nice we need to generate the syntax.css:

$ hugo gen chromastyles --style=monokai > ./static/syntax.css

And hook it up in config.toml:

  customCSS = [

Image and things 🤳

Resize using imagemagick

Create a /static/img folder using:

$ mkdir -p static/img

Then we can resize whatever image I’m going to use (usually for the post cover photos):

$ convert my-image.jpg -resize 1500x1500 ./static/img/my-image.jpg

Convert PNG to JPG

I use Alchemy to convert from PNG to JPG https://dawnlabs.io/alchemy/, although I thing ImageMagick can do it as well.

Deployment and more

Deployment script

./scripts/deploy.sh, as taken from the Hugo docs:

echo -e "\033[0;32mDeploying updates to GitHub...\033[0m"
# Build the project.
hugo # if using a theme, replace with `hugo -t <YOURTHEME>`
# Go To Public folder
cd public
# Add changes to git.
git add .
# Commit changes.
msg="rebuilding site `date`"
if [ $# -eq 1 ]
    then msg="$1"
git commit -m "$msg"
# Push source and build repos.
git push origin master
# Come Back up to the Project Root
cd ..

“Cleaning up” with Node and npm scripts 🙄

This is the full package.json (minus the stuff that’s not necessary):

    "name": "codewithhugo",
    "description": "Repo for content of codewithhugo.com.",
    "config": {
      "syntax": "static/syntax",
      "overrides": "static/overrides",
      "images": "static/img"
    "scripts": {
      "build": "concurrently --names \"SYNTAX,OVERRIDES,IMAGES\" -c \"bgBlue.bold,bgMagenta.bold,bgYellow.bold\" \"npm:build:css:syntax\" \"npm:build:css:overrides\" \"npm run build:imageoptim\"",
      "predeploy": "npm run build",
      "deploy": "./scripts/deploy.sh",
      "serve": "./scripts/serve.sh",
      "start": "npm run dev",
      "dev": "concurrently --names \"CSS,HUGO\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm:watch:css\" \"npm:serve\"",
      "watch:css": "concurrently \"npm run build:css:syntax -- --watch --map inline\" \"npm run build:css:overrides -- --watch --map inline\"",
      "build:css:syntax": "csso -i $npm_package_config_syntax.css -o $npm_package_config_syntax.min.css --stat",
      "build:css:overrides": "csso -i $npm_package_config_overrides.css -o $npm_package_config_overrides.min.css --stat",
      "build:imageoptim": "imageoptim $npm_package_config_images"
    "author": "Hugo Di Francesco",
    "homepage": "https://github.com/HugoDF/codewithhugo#readme",
    "devDependencies": {
      "concurrently": "^3.6.0",
      "csso-cli": "^1.1.0",
      "imageoptim-cli": "^2.0.3"

Let’s walk through what happens when I run npm run deploy, these three tasks are the main ones:

"build": "concurrently --names \"SYNTAX,OVERRIDES,IMAGES\" -c \"bgBlue.bold,bgMagenta.bold,bgYellow.bold\" \"npm:build:css:syntax\" \"npm:build:css:overrides\" \"npm run build:imageoptim\"",
"predeploy": "npm run build",
"deploy": "./scripts/deploy.sh",

A feature of npm is that if you have a script called something you can defined another script presomething that will run before something whenever it’s called. So before deploying, we npm run build. npm run build runs a couple of tasks in parallel with concurrently (https://www.npmjs.com/package/concurrently):

  • npm:build:css:syntax which is concurrently-specific shorthand for npm run build:css:syntax
  • npm:build:css:overrides which is concurrently-specific shorthand for npm run build:css:overrides
  • npm run build:imageoptim

Minify and optimise CSS

"build:css:syntax": "csso -i $npm_package_config_syntax.css -o $npm_package_config_syntax.min.css --stat",
"build:css:overrides": "csso -i $npm_package_config_overrides.css -o $npm_package_config_overrides.min.css --stat",

build:css:syntax and build:css:overrides pretty much run the same command but on different files. Namely the files are $npm_package_config_syntax.css and $npm_package_config_overrides.css which interpolates npm_config syntax and evaluate to static/syntax.css and static/overrides.css (see config.syntax and config.overrides in package.json. We use https://github.com/css/csso-cli, which pretty much just minifies the CSS.

The optimised CSS files get output as static/syntax.min.css and static/overrides.min.css, which I also update in the config.toml:

  customCSS = [


A similar approach is used for build:imageoptim:

"build:imageoptim": "imageoptim $npm_package_config_images"

It runs imageoptim-cli on the contents of $npm_package_config_images (which expands to static/img).

Running in development mode

Now that I’ve got a build step in place… the problem is that I need to watch for changes etc in development as well, which are the following scripts, from package.json:

"serve": "./scripts/serve.sh",
"start": "npm run dev",
"dev": "concurrently --names \"CSS,HUGO\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm:watch:css\" \"npm:serve\"",
"watch:css": "concurrently \"npm run build:css:syntax -- --watch --map inline\" \"npm run build:css:overrides -- --watch --map inline\"",


hugo serve . -F
# -D will serve even draft posts
# -F will serve future posts

I like not having to change the dates of my posts when they’re not ready to be published yet, so I use the -F flag. If I don’t want a certain post to appear in development I’ll use draft: true in the frontmatter. npm run dev just runs the csso-cli tasks in watch mode as well as the Hugo dev server.

Getting a full RSS feed 😄

The default Hugo RSS feed doesn’t have the full post contents, it just has the excerpt (what would display on the homepage tiles in my case). The problem with that is… well that the RSS feed doesn’t have full post contents. I knew it was a problem but as usual I never got round to fixing it until I embarrassed myself while promoting it 😄:

If you have a blog and write/share useful things (big or small) about Web design / front-end dev and you provide an RSS feed to the blog, please respond to this tweet w/ the URL for it — I’d like to add more useful content feeds to my reader. 🙌🏻💃&mdash; Sara Soueidan (@SaraSoueidan) 20 July 2018

And my reply to that:

Hey there, I write at https://codewithhugo.com, I don't think the RSS feed I've got actually has the full posts though 🙈

— Hugo Di Francesco (@hugo__df) 21 July 2018

To fix that I found https://randomgeekery.org/2017/09/15/full-content-hugo-feeds/, (where there’s a full explanation of what we’re doing). Essentially it boils down to creating the following layouts/_default/rss.xml:

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <title>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
    <link>{{ .Permalink }}</link>
    <description>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
    <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
    <language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
    <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
    <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
    <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
    <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
    {{ with .OutputFormats.Get "RSS" }}
        {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
    {{ end }}
    {{ range .Pages }}
        <title>{{ .Title }}</title>
        <link>{{ .Permalink }}</link>
        <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
        {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
        <guid>{{ .Permalink }}</guid>
        <description>{{ .Content | html }}</description>
    {{ end }}

Which is just the default template except instead of:

<description>{{ .Summary | html }}</description>

We do:

<description>{{ .Content | html }}</description>

This make the built index.xml file huge since it’s putting every post’s full contents, so again on https://randomgeekery.org/2017/09/15/full-content-hugo-feeds/, I decided to limit the number of feed items to 15 (instead of 20 suggested in that post) that’s an update in config.toml:

rssLimit = 15

Full credit for this to this post https://randomgeekery.org/2017/09/15/full-content-hugo-feeds/, which actually explains the how and why of what has been done.

Cloudflare setup

I flipped on a couple of settings, notably forcing HTTPS: Cloudflare Dashboard > Crypto > Always use HTTPS > On and in the same section Crypto > Automatic HTTPS Rewrites > On.

I increased the Caching > Browser Cache Expiration to 8 days (could/should be more but I don’t want to have to version my assets) and switched on Caching > Always Online™ so that should GitHub Pages fall down, Cloudflare will still serve my content and all would be well.

That’s pretty much all of my Hugo setup for codewithhugo.com short of a couple of extra images and setting strings in config.toml.

unsplash-logoParker Whitson


Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Get The Jest Handbook (100 pages)

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.