DEV Community

Cover image for Switching the lights on: Hugo vs Hugo config files
Hugo Di Francesco
Hugo Di Francesco

Posted on • Updated on • Originally published at

Switching the lights on: Hugo vs Hugo config files

A simple Hugo blog setup

The story of getting 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 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"

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 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 🎨 didn’t have syntax highlighting for a while, 😕 and I even started whining.

<blockquote><p>When <a href="">@ThePracticalDev</a> has better syntax highlighting than your blog 😂😂 does anyone  know how to sort out <a href="">@GoHugoIO</a> syntax highlighting <a href=""></a> <a href=""></a></p>&mdash; Hugo Di Francesco (@hugo__df) <a href="">13 April 2018</a></blockquote>

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="//"></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, although I thing ImageMagick can do it as well.

Deployment and more

Deployment script

./scripts/, 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",
    "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/",
      "serve": "./scripts/",
      "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": "",
    "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/",

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 (

  • 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, 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/",
"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 😄:

<blockquote><p>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. 🙌🏻💃</p>&mdash; Sara Soueidan (@SaraSoueidan) <a href="">20 July 2018</a></blockquote>

And my reply to that:

Hey there, I write at, 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, (where there’s a full explanation of what we’re doing). Essentially it boils down to creating the following

<rss version="2.0" xmlns: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 --</generator>{{ with .Site.LanguageCode }}
    <language>{{.}}</language>{{end}}{{ with }}
    <managingEditor>{{.}}{{ with $ }} ({{.}}){{end}}</managingEditor>{{end}}{{ with }}
    <webMaster>{{.}}{{ with $ }} ({{.}}){{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 }}<author>{{.}}{{ with $ }} ({{.}}){{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, 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, 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 short of a couple of extra images and setting strings in config.toml.

Parker Whitson

Top comments (5)

erebos-manannan profile image
Erebos Manannán

You don't seem to explain any reason for using CloudFlare on top of GitHub pages. Care to elaborate?

hugo__df profile image
Hugo Di Francesco
  • HTTPS (pretty sure it didn't use to be a thing with custom domains on GitHub pages)
  • CDN
  • DNS
erebos-manannan profile image
Erebos Manannán

Yea, nowadays GitHub Pages supports HTTPS with custom domains:

DNS is really irrelevant to GitHub - you can handle it anywhere. I generally use e.g. Google Cloud Platform/Azure/AWS's DNS capabilities as they're super cheap and convenient.

And a CDN is probably not going to make a massive difference to your GitHub Pages -website - not sure what you're hosting though hehe.

Anyway, interesting to hear your reasoning.

Thread Thread
hugo__df profile image
Hugo Di Francesco

You're right for most of the above points, I like cloudflare to do almost application level stuff (eg. domain redirect and to be able to set caching policy for assets (GH pages doesn't allow that).

It's not crucial but it's been quite useful to get some stuff done.

tamouse profile image
Tamara Temple

Great write-up. I do something similar with Jekyll, having been primarily in the Ruby on Rails back-end world. Great to see folks sharing their setups!