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"
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 🏃♂️
- Picking a theme and overriding little things 🖼
- Enabling syntax highlighting 🎨
- Image and things 🤳
- Deployment and more
- Cloudflare setup
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
:
[params]
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.
@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
:
pygmentsUseClassic=false
pygmentsCodefences=true
pygmentsUseClasses=true
This stumped me for months, finally it turns out I was doing this
[params]
pygmentsUseClassic=false
pygmentsCodefences=true
pygmentsUseClasses=true
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
:
[params]
customCSS = [
"overrides.css",
"syntax.css"
]
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:
#!/bin/bash
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"
fi
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 isconcurrently
-specific shorthand fornpm run build:css:syntax
-
npm:build:css:overrides
which isconcurrently
-specific shorthand fornpm 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
:
[params]
customCSS = [
"overrides.min.css",
"syntax.min.css"
]
Imageoptim
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\"",
./scripts/serve.sh
:
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 😄:
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">
<channel>
<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 }}
<item>
<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>
</item>
{{ end }}
</channel>
</rss>
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
.