Customising categories URLs in Hugo revisited

An advanced hack to customise categories URLs in Hugo

637 words · 3 min read

A while ago, I wrote a simple workaround for customising URLs of categories pages in Hugo. The purpose was to remove the word “categories” from the URLs of categories pages, a feature (still) missing in Hugo.

That workaround is simple, and it replicates the behaviour in my old Jekyll site, though it isn’t perfect: it can only generate one single page for each category without pagination, while the “original” categories pages supports pagination out of the box. The lack of pagination could be problematic when you have a large number of items under one categories. Since the feature request for customisable taxonomies URLs isn’t taken up, I needed a new hack.

The workaround describes here takes advantage of the fact that Hugo, as a static site generator, generates static files that can be modified and moved around on disk before deploying to a production server. The following gives you a general idea of how this is done. The exact codes will vary according to your site template and workflow.

1. Change the Hugo Template

This step removes the string /categories from HTML files generated by Hugo.

First, change the template for categories pages, which is located in layouts/taxonomy/category.html. Use the replace function to remove all instances of /categories from the pagination links:

<ul class="pager">
  {{ if .Paginator.HasNext }}
    <li class="next">
        <a href="{{ replace .Paginator.Next.URL "/categories" "" }}">Older Posts</a>
  {{ end }}
  {{ if .Paginator.HasPrev }}
    <li class="previous">
        <a href="{{ replace .Paginator.Prev.URL "/categories" "" }}">Newer Posts</a>
  {{ end }}

We must also remove /categories elsewhere. For example, in partials/head.html, change

<link rel="canonical" href="{{ .Permalink }}" />:


<link rel="canonical" href="{{ replace .Permalink "/categories" "" }}" />

If done correctly, the page /categories/category-name/ should now stop working properly—the pagination links should now point to /category-name/page/num/, which is a dead link—and the page /category-name/ doesn’t exist yet. To make it work on your production site, you have to move all the files to the correct directories.

2. Move (and minify) the HTML files to the correct locations

After removing all traces of /categories from the Hugo-generated HTML files, you need to move the files into the right places.

Assuming the output destination of hugo is /output_unprocessed/, the Gulp tasks below will minify and move the HTML files to the correct places: folders and files inside categories will be moved up one level, and the categories folder will no longer exist:

var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');
// More declarations here //

gulp.task('minify1' , function() {
  return gulp.src(['./output_unprocessed/**/*.html','!./output_unprocessed/categories/**/*.html'])
    .pipe(htmlmin({collapseWhitespace: true, conservativeCollapse: false, minifyCSS: true, minifyJS: true, minifyURLs: true}))

gulp.task('minify2', ['minify1'], function() {
  return gulp.src(['./output_unprocessed/categories/**/*.html'])
    .pipe(htmlmin({collapseWhitespace: true, conservativeCollapse: false, minifyCSS: true, minifyJS: true, minifyURLs: true}))

// More tasks here //

gulp.task('deploy', ['minify1','minify2','x', 'y', 'z']); // So that these tasks are run sequentially

3a. Build and run Gulp locally and deploy the output

If everything is set up correctly, the site is ready to be deployed after running hugo and gulp deploy.

3b. Build and run Gulp on Travis CI

If you use continuous integration services such as Travis CI, you can incorporate all the steps into your Travis CI build process. Below is a snippet of my .travis.yml used in my Travis CI build process:

# More codes here...

- npm install -g gulp
- npm install gulp
- npm install gulp-htmlmin
- npm install gulp-base64

- hugo
- gulp deploy

# More codes here...

In my deployment process, Travis CI will push the output to my Github repository, which will then be pulled by my webserver.

4. Watch your new site goes live

Voila! The site is now live with the new hack!

Admittedly, this hack is rather dirty. I would still like to see out-of-the-box support for customisable categories URLs, though this new workaround produces the exact result I have been wishing to achieve.

 Tech    25 Jul, 2016
 Hugo    Web Development    Static Site  
Copyright © Peter Y. Chuang 2019

Peter Y. Chuang


Peter Y. Chuang is a novelist, short story writer, and a music critic. When he’s not writing or reading, he’s probably listening to classical music or tinkering with his computers. He uses Linux (current distro of choice: Arch Linux). Read more about his Linux stuff.

You may also like...