621 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">
<span>
<a href="{{ replace .Paginator.Next.URL "/categories" "" }}">Older Posts</a>
</span>
</li>
{{ end }}
{{ if .Paginator.HasPrev }}
<li class="previous">
<span>
<a href="{{ replace .Paginator.Prev.URL "/categories" "" }}">Newer Posts</a>
</span>
</li>
{{ end }}
</ul>
We must also remove /categories
elsewhere. For example, in partials/head.html
, change
<link rel="canonical" href="{{ .Permalink }}" />:
to
<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}))
.pipe(gulp.dest('../output_final'))
});
gulp.task('minify2', ['minify1'], function() {
return gulp.src(['./output_unprocessed/categories/**/*.html'])
.pipe(htmlmin({collapseWhitespace: true, conservativeCollapse: false, minifyCSS: true, minifyJS: true, minifyURLs: true}))
.pipe(gulp.dest('../output_final'))
});
// 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...
before_script:
- npm install -g gulp
- npm install gulp
- npm install gulp-htmlmin
- npm install gulp-base64
script:
- 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.