We’ve found Hugo to be a wonderful static site generator; it provides sensible default content patterns, has no opinions on information architecture or frontend, doesn’t get in the way of defining content types and taxonomies, and offers absurdly fast build times.

Before getting any deeper into how we’ve used Hugo, what use is a static site generator to a modern software company that specialises in building robust data-focused systems? Well, like the skilled barber with the bad haircut, we had let our own image slip while busily servicing our clients.

As we’ve moved towards a more product-oriented approach to both our internal and our client work, a situation arose whereby our existing site no longer represented us accurately. We had very limited time available initially to put that right; we needed a solid base upon which we could regularly iterate the design and content of our own site, using an Agile methodology.

We considered the short and long-term implications of using our usual stack, and compared that to using a static site generator. While we have extensive experience of using Wagtail to power large client sites, monolithic content-management systems often add an overhead to the engineering time required on a project, particularly when data models change during development.

During these discussions we realised that we simply didn’t have a use for the conveniences that many content-management systems provide; our design and engineering team is very comfortable writing Markdown in a text editor and using the terminal to push commits to version control, so any effort expended to fit our new site into a CMS would be extraneous.

Modern static site generators are perfect for these needs, and they bring with them benefits like incredibly fast page render times, high security, a cost-efficient and reliable scaling path, and simple semantic versioning for the entire site, including the site’s content. Exciting work is being done to allow static sites to offer features traditionally only available to dynamic sites, but it’s still early days for ideas like the JAMstack and Functions as a service, and we’re careful to only use reliable and well-supported tools with solid, established communities.

There’s a wealth of well-documented and mature static site generators available today, and though we were impressed by Gatsby’s features, we opted for Hugo due to the robust functionality it offers without plugins, as well as our team’s previous experience with Golang.

Though we are considering integrating into our site in the future a layer of Git-based content-management, so that our non-technical colleagues can edit content, in the short-term this wasn’t an option. We still needed each page’s content area to be more bespoke than a simple Markdown output, and Hugo’s built-in shortcodes helped with that.

An example of where our needs outgrew the scope of Hugo’s defaults is headings. Our design has a page content area constrained to a maximum width, and that maximum width is double the width of a line of paragraph copy at its optimal line-length (the length required for good readability). The paragraph copy itself is left-aligned, but pushed to the right-hand side of its container on larger viewports.

Introducing headings to this design presented an issue; left-aligned or centre-aligned headings made the copy look fragmented and difficult to scan, while pushing a heading to the right, with the differing optimal line-length that each heading level has, compromised the clear vertical line that the paragraph copy had introduced and left a large area of white space on the left of the container on some pages.

We settled upon a design where headings took up the left-hand half of the container, and sat vertically-aligned with the paragraph that followed it.

An option in implementing this design was to address the problem with just CSS, introducing no extra markup. We could give the containing element a flex-wrap of wrap, and using the adjacent sibling selector (+) we could target paragraphs that followed a heading, declaring that both the heading and the following paragraph should take up 50% of a row.

This solution works but introduces some undesirable behaviour. If the paragraph following a heading is very short and the heading itself is quite long then a lot of white space displays below the paragraph, making it unclear that there’s no semantic break between it and the next paragraph. We would also have to pre-empt every type of content that could follow a heading and write selectors to handle them; admittedly not a long list at present, but this would undoubtably grow with time and would need updating, making it a brittle solution that introduces to the project a form of technical debt.

The solution that we settled upon was to create a shortcode which gives us a new type of content block. This block wraps a heading and its following content in dividers, with a divider containing both. This gives the content editor the ability to wrap some or all of the text content following a heading so that we don’t run into any unwanted white space, while maintaining the semantic integrity of the content itself, and also allowing for elements like full-width images which break up paragraph content.

Here’s the file declaring the shortcode as we’ve implemented it in Hugo:

1
2
3
4
5
6
7
8
<div class="heading_block">
    <div class="heading_block__heading">
        <h{{ .Get "heading_number" }} class="heading_block__heading__text">{{ .Get "heading_content" }}</h{{ .Get "heading_number" }}>
    </div><!-- .heading_block__heading -->
    <div class="heading_block__content">
        {{ .Inner }}
    </div><!-- .heading_block__content -->
</div><!-- .heading_block -->

The content editor can now use the shortcode in a Markdown content file like this:

1
2
3
4
5
6
    {{% heading heading_number="2" heading_content="Here is a level 2 heading" %}}
    Here is a very short paragraph, followed by a list:

    * Here is a list item.
    * Here is another.
    {{% /heading %}}

This solution would likely already be overwhelming for a non-technical content editor, but as our content will be added by engineers we are able to introduce a variety of shortcodes, giving us the modular functionality offered by some of the more complex content-management systems while retaining the benefits of using a static site generator.