Hugo Static Site Generator Tips & Tricks

turbin3

BuSo Pro
Joined
Oct 9, 2014
Messages
613
Likes
1,285
Degree
3
The purpose of this thread is to aggregate as many Hugo generator tips and tricks into one place as possible. This will make for a great reference for those looking for a refresher with Hugo, or beginners looking to make sense of where to start. First off, a few of mine:

Dynamically Generated Table of Contents
I must have spent hours on this. In hindsight, I feel like an idiot. After trying and failing with a bunch of different methods......I found a totally copy/paste method, directly from the Hugo docs, that totally works! :wink: Hopefully this will save some people a lot of pain:

Hugo Table of Contents

Basically, it takes the headings in your post and uses them to generate a nav element with an unordered list of those headings. This is excellent for lengthy posts where you want to display an easy to use sub-nav, within the post, for navigating around it.

The awesome thing is, you can simply paste this into your relevant theme template, wherever you prefer to use it. Then just style with a bit of CSS, and it's kind of a set it and forget it affair. Just write your posts, add headings when you want, and the in-post nav will generate itself.

Using Find & Replace Regex in Hugo
There's actually a built in function for this. This can be really useful if you need to do something like replace all of a particular type of thing sitewide, like maybe reformatting a particular word, URL, etc. Just be careful, as regex replace is powerful and indiscriminate if applied incorrectly.

I've used this before with sites I've migrated to Hugo, that had a significant number of posts. Sometimes you might have things like old links, certain words (brand names, etc.) etc. that are too numerous to deal with manually. There are of course other ways to deal with this, including more permanent ways, but a regex replace is a quick and dirty method to just get the job done.

Base64 Encoding & Decoding
Yep, Hugo even has built in functionality for base64 encoding and decoding! There are some particularly cool possibilities with this. For example, for certain purposes you may want to base64 encode an item to protect against low-barrier-to-entry site scrapers that don't account for these things (trying to gobble up your email addresses and stuff!).

So with this, maybe you could encode an element and then decode it elsewhere when needed. Maybe in some cases you might use Hugo in a theme template to encode a certain element, and elsewhere maybe you use Javascript to decode it. This could help protect against at least the low-barrier stuff from scraping form data, emails, other contact info, etc.

HTML / CSS / JS Minification Build With NPM
If you don't have NodeJS and NPM installed (NodeJS installs both), I highly recommend it! NPM is extremely handy for quickly getting a set of scripts and commands together to add capabilities to pretty much anything. In this case, we'll basically add a processing pipeline to minify our HTML, CSS, and JS files. I can't remember why, but with this particular setup I wasn't concatenating files, probably because some sort of UI issues I was too lazy to debug.

If this is your first time working with NPM, once you have it installed you'll want to go to the main directory for whichever Hugo site you're working with. Then run this command:
Code:
npm init
This will create a package.json file in the root of your site's directory. The package.json is basically the "settings" file. NPM will basically create a "project" in this site's directory, and the files of any packages you choose to install will go under a "node_modules" directory. If you gaze into this directory in horror, don't worry! You will rarely if ever need to venture in there.

We configure our settings in package.json, maybe create a few other files in our site's root directory for certain packages we may have installed (these are also basically "settings" files), and NPM does the work for us! Here's a package.json you can work with for this project:
Code:
{
  "name": "My Site",
  "version": "1.0.0",
  "description": "Check out my awesome site!",
  "main": "index.html",
  "scripts": {
    "server": "hugo server -w -v",
  "build": "hugo -v && gulp build",
  "deploy": "aws s3 sync public/ s3://www.examplesite.com/ --delete"
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.5.0",
    "babel-register": "^6.5.2",
    "gulp": "^3.9.1",
    "gulp-cli": "^1.2.1",
    "gulp-htmlmin": "^1.3.0",
    "gulp-clean-css": "^3.0.4",
    "gulp-uglify": "^2.1.2",
    "gulp-shell": "^0.5.2",
    "run-sequence": "^1.1.5"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  },
  "author": "Turbin3",
  "license": "ISC"
}

So, looks like there's a LOT going on here! Don't worry, I'll break it down. The top part is usually your main configuration, site info, basic or custom commands, etc. Pay attention to the "devDependencies" section, as well as the "babel" section below. If you already have NPM and copy those sections into your package.json, you can simply run the "npm install" command from your command line and NPM will install those packages for you!

Creating & Running Custom NPM Commands
There are also a few other useful things in this example. One is a custom command ("server") to run the Hugo server. In this case, I'm using standard Hugo flags to run the local test server for that site, with the "watch" flag (-w or --watch) so it will live reload on changes. I'm also using the -v command (verbose) which can be useful for debugging and seeing more error info.

Deploying to AWS S3 With NPM
Additionally, in this instance I also have the AWS CLI installed on this particular system, and I've added an NPM command ("deploy") to deploy the built site (which gets built to the "public" folder) to S3. This command uses the "sync" option as well as the --delete flag, which will update only the files that have been changed, added, or removed, which is super useful.

Setting Up Gulp + NPM To Minify Resource Files
Now let's setup a javascript file to setup the minification process for us. Create a file in the root of your Hugo site called "gulpfile.babel.js". I forget if this is already created for you, once you've run the "npm install" command to install dependencies. If not, just create the file and paste this in:
Code:
import gulp from 'gulp'
import htmlmin from 'gulp-htmlmin'
import cleanCSS from 'gulp-clean-css'
import uglify from 'gulp-uglify'
import runSequence from 'run-sequence'
import shell from 'gulp-shell'

gulp.task('hugo-build', shell.task(['hugo']))

gulp.task('minify-html', () => {
  return gulp.src('public/**/*.html')
    .pipe(htmlmin({
      collapseWhitespace: true,
      minifyCSS: true,
      minifyJS: true,
      removeComments: true,
      useShortDoctype: true,
    }))
    .pipe(gulp.dest('./public'))
})

gulp.task('minify-css', () => {
  return gulp.src('public/**/*.css')
    .pipe(cleanCSS())
    .pipe(gulp.dest('./public'))
})

gulp.task('minify-js', () => {
  return gulp.src('public/**/*.js')
    .pipe(uglify({
      preserveComments: 'all',
    }))
    .pipe(gulp.dest('./public'))
})

gulp.task('build', ['hugo-build'], (callback) => {
  runSequence(['minify-html', 'minify-css', 'minify-js'], callback)
})

Basically, the way this works is, when you run the "npm run build" command ("build" is the custom command we created in the package.json above), it runs Hugo's built in command to build the site to the "public" folder. NPM then runs the "gulp build" command automatically after this. The code in this gulpfile.babel.js file then minifies the files in your public folder, which are the ones you would be deploying to wherever you decide to host the site. The idea here is, we're not making any permanent changes. This is just post-processing the built site to optimize it a bit, which is a pretty useful concept.

That's about all I have tonight. I'd also encourage anyone new to Hugo to ask questions here! I know many of you are getting started with Hugo. I'm sure @built has a few questions. When I first started with it, there was a bit of a learning curve. The Hugo docs have improved quite a bit, though I'd say the "usability" is still a bit lacking for those that are not quite traditional developers.
 
Great post. How come Hugo doesn't output the site locally like Jekyll? Unless I haven't configured it correctly. With Jekyll, the generated content is in a _site folder, whereas with Hugo I have nothing.
 
Great post. How come Hugo doesn't output the site locally like Jekyll? Unless I haven't configured it correctly. With Jekyll, the generated content is in a _site folder, whereas with Hugo I have nothing.
By default, Hugo generates files in "public" folder. just run "hugo"
 
@turbin3 is this how to create custom page templates? For example: I want a single page template that has no sidebar.

Code:
Front matter:

type: nosidebar
layout: nosidebar

Template folder inside layout folder:

layouts/nosidebar/nosidebar.html

This works for me, I'm just wondering if its the best way to do things? I know with Jekyll I just have to create the file and call it with my front matter, but with Hugo I have to include the type as well?
 
That's how I do it. By having specify both you can have a bit more organized flexibility.
 
Yep, that's exactly how I do it as well. It seems a bit excessive/verbose to me, but that's what I found that works. I haven't really kept up with the documentation and specs, and Hugo is evolving pretty fast, so I'm not sure if any "better" ways have come along.
 
@turbin3 Not specific to Hugo, but static sites in general.
How do you handle images? for example, I use featured images for my posts at 1200x600. I would like to have 300x200 image on my recent posts sidebar and 400x300 on homepage/404 page.

What are you using for site-wide search?
 
@turbin3
How do you handle images? for example, I use featured images for my posts at 1200x600. I would like to have 300x200 image on my recent posts sidebar and 400x300 on homepage/404 page.

sidebar .recent-posts img{
/** set dimensions here **/
}
 
sidebar .recent-posts img{
/** set dimensions here **/
}
Speed implications? Showing 1200px image into 300px image is not ideal IMO.

I think better solution would be to resize images into thumbs and other dimension on the build step.

Another option i looked up is to use something like Cloudinary or similar.
 
I haven't see a standardized thumbnail solution for Hugo. I don't keep up to date much with other CMS' and generators like Jekyll, Grav, or others.

I have seen a lot of people in the Hugo world creating their own solutions. I'm not going to recommend any specific examples. Main reason is, the lack of standardization and variety of options. I think it's too easy to get locked into someone else' potentially flawed code. I don't want to steer anyone wrong.

What I'd rather do is give a simple solution that anyone can use easily. This is not the most scalable option, but it works, and anyone can do it easily. For generators or CMS' that store content in markdown files, simply add another variable in your front matter. Example:

Code:
+++
title = "My title"
desc = "My description"
image = "main-image.png"
thumbnail = "main-image-tn.png"
+++

In that example, we're using TOML for front matter (YAML and JSON are other common ones). The idea is, make your thumbnail manually and link it in the front matter of your post or page. Then add some minor code to the relevant page templates, to have a post feed use the thumbnail instead. This is quick, easy, and will require almost no code. Here's an example from Hugo:

Code:
{{ $posts := .Paginate (where .Data.Pages "Type" "blog") }}

{{ range first 5 $posts.Pages }}

<div class="post-image">
    
    {{ if isset .Params "thumbnail" }}
    
    <img src="/img/{{ .Params.thumbnail }}">
    
    {{ else }}
    
    <img src="/img/placeholder.png">
    
    {{ end }}

</div>

{{ end }}

In that example, we have a "loop" where we're generating a feed of 5 blog images. We're also using a conditional statement in the form of an if / else. That protects us, in case we try to call a variable that doesn't exist, and in some circumstances things might crash when trying to generate. Also, that let's us offer alternatives when we don't have a thumbnail.

That's especially great if you're plugging in a bit of code to an existing site, but don't yet have thumbnails, or only have them for some posts. Just add in a few placeholders as fallbacks. You could even go in depth with this and create placeholders for each category if you really want to.

For thumbnails, one quick and dirty solution to generate in bulk is Easy Thumbnails.

Long term, you'll want a better solution to automate this, but that's a problem for another day.

As far as search in Hugo or some other static site generators, there's 2 decent options I've found.
  • Lunr JS - Should work fine up to a few hundred posts/pages. More work to implement.
  • Algolia - Will work for LOTS of records. Easier to implement. For larger sites, may require a paid account.
With Lunr and Hugo, the idea is to generate a JSON index of the posts on your site, and use Lunr to search it. There are a number of ways to generate that JSON with Go, Hugo, Gulp, or quite a few other methods. When I have time in the next day or 2, I'll try to cover a few of those ways, in particular using some built in Hugo functions.
 
Speed implications? Showing 1200px image into 300px image is not ideal IMO.

I think better solution would be to resize images into thumbs and other dimension on the build step.

Another option i looked up is to use something like Cloudinary or similar.

I use GraphicsMagick (gulp library here) there are multiple plugins that will work for your npm files. NOTE: requires you to install a binary on your system. If you have individual questions ask away but it's mostly straight forward.
 
Back