Building This Blog Using Hugo

Blog | . Edited . 5 min read (1250 words).

The technical design and building blocks of this blog.

Screenshot of Visual Studio Code with blog source
code open

Since that is my primary interest, I will focus on the high-level technical design similar to what one would find in a design document and mostly ignore the code and implementation details. Read the documentation linked to for details.

Requirements

I wanted a blog where I could write about mainly technical and mathematical topics, both as a way of learning and as a way of sharing knowledge. As such, the focus should be on the content and I clearly needed support for writing mathematics and quoting source code. At the same time, I also wanted to be able to extend the website to include for example pages about projects, link collections, short e-books, or anything similar in the future. I also wanted a clean Scandinavian design on the minimalist side.

My requirements looked something like this:

Based on this, I set out to investigate my options.

Technical design choices

From the beginning, I had my eyes on trying out some kind of static site generator (see also Jamstack). These kinds of tools have become popular in recent years and for good reasons. The basic idea is that you create your content in some way separate from the website itself and then generate the entire website based on the latest content ahead of time. All you need to publish online in the end are the generated static web pages and related content.

The advantages of this are quite obvious:

Naturally, I wanted to go down this path as it seems a perfect fit. The potential drawback could be limited flexibility, because of the tool or the concept itself. With this in mind, I looked into a few different options and eventually determined that Hugo fulfilled my requirements and is nice to work with.

Hugo is available as packages for most Linux distributions, so it’s easy to install. The next choice to decide on was whether to start with an existing Hugo theme. Depending on one’s goals and taste, this is likely a very good option. I had a look at existing themes and deciding to continue without, since one of my requirements were to keep control over these aspects. Therefore, I instead created my own layout templates from the start.

At this point, I made a detour to look into hosting options. An Ex-Googler as I am, I went for Firebase hosting, which is both very easy to use with these tools and works great through Google’s worldwide CDN. Headers can be controlled to adjust caching for different kinds of files. New versions can easily be deployed through the Firebase CLI.

Next, I looked into the options for mathematical equations. I already knew about MathJax, but also found out about the newer $\KaTeX$ which I ended up choosing.

Even though Hugo has a built-in alternative, I also decided to set up webpack together with NPM for bundling JavaScript, CSS, and Sass resources. This works well for bundling my own such resources together with those from third-party NPM packages. To tie everything together, I have a simple Makefile. Hugo and webpack can also both run in watch-mode at the same time and will detect changes in sequence.

I still have one main area left to explore: web design. Here, I decided to try something new and use Bootstrap, which fits well into the webpack workflow. I only use some parts, but it has helped a lot in combination with my own HTML, Sass, and JS too. For some extensions of my own, I needed some small animations and decided to use anime.js, which turned out to be very useful even for relatively easy animations (related to the menu).

To get basic visitor statistics, I use Google Analytics with analytics.js and all tracking through cookies and similar disabled.

As usual, I’m also using Git, GitHub, Visual Studio Code, and Arch Linux for development. At this time, the source code repo is private.

In summary, I decided to use this tech stack:

Hugo

One of the best things about Hugo is that you can add your own shortcodes. This makes it easy to generate some custom HTML easily from within your content files in a reusable way.

As an example, I have this shortcode template called danger.html:

<div class="alert alert-danger" role="alert">
    {{- .Inner | .Page.RenderString -}}
</div>

Similarly, I’ve created a partial for the layout templates that shows the estimated reading time. It’s based on this article which suggests that the average reading speed is 238 words per minute. The partial then becomes:

{{ math.Round (div (countwords .Content) 238.0) | default 1 }}

There’s a lot more to say about Hugo, but I will simply suggest reading the official documentation and being creative. It can’t do everything, but it can do a lot and can always be extended if needed since it’s open source.

Webpack

Webpack is a great JavaScript tool. The only downside is that it’s so flexible that it’s hard to configure it at times. Reading the documentation is useful, of course, but examples are good too. Here’s my Webpack config, without Katex:

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js',
  },
  mode: 'production',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'assets/static/bundle'),
  },
  plugins: [
    new CleanWebpackPlugin(),
  ],
  module: {
    rules: [{
      test: /\.(scss)$/,
      use: [
        { loader: 'style-loader' },
        { loader: 'css-loader' },
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: {
              plugins: [
                ['autoprefixer', {}],
              ],
            },
          },
        },
        { loader: 'sass-loader' },
      ],
    }],
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        test: /\.js(\?.*)?$/i,
        extractComments: /^\**!|@preserve|@license|@cc_on|license/i,
      }),
    ],
  },
};

You may need to install some of these NPM packages too:

  "devDependencies": {
    "autoprefixer": "^10.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^5.0.1",
    "file-loader": "^6.2.0",
    "postcss": "^8.2.1",
    "postcss-loader": "^4.1.0",
    "sass": "^1.30.0",
    "sass-loader": "^10.1.0",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^5.0.3",
    "webpack": "^5.11.0",
    "webpack-cli": "^4.2.0"
  }

Some of the above packages are only used for bundling up Katex (file-loader and css-loader), which is fairly easy to set up too.

Google Analytics

I’ve found that the following JavaScript disables all important tracking for a more privacy-focused use of Google Analytics:

ga('create', 'UA-XYZ', {
    cookieDomain: 'none',
    storage: 'none',
    storeGac: false,
});
ga('set', 'allowAdFeatures', false);
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');

I’ve only found a way to do this using analytics.js.

Conclusions

I’m very happy with the website / blog so far and it feels like I made the right choices for me. The technology is simple and still sufficient to not be limiting. I can also work with all my favourite development tools. I can definitely recommend a similar setup, tuned to your own preferences!

See also

Related content: