Original post

Welcome to LWN.net

The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider accepting the trial offer on the right. Thank you for visiting LWN.net!

Static -site generators take page content written in a markup language and render it into fully baked HTML, making it easy for developers to upload the result and serve a web site simply and securely. This article looks at Hugo, a static-site generator written in and optimized for speed. It is a flexible tool that can be configured for a variety of use cases: simple blogs, project documentation, larger news sites, and even government services.

Background

Static-site generators have probably been around since the invention of HTML: most sites have a common header and footer, and site owners don’t want to copy and paste that manually into each of their pages. Back then, developers would be more likely to write a few dozen lines of custom Perl or Bash to string their HTML together. That approach definitely works, but today’s static-site generators are faster and more powerful than a simple custom script.

A lot of sites handle common headers (and the like) by dynamically rendering the content for every page view; for example, by using PHP connected to a database. In 2020, developers might be more likely to use a React frontend connected to an HTTP API that serves the content. Both of these setups, however, are overkill when a site just needs to serve some mostly static content.

Many personal sites and blogs, as well as project and company documentation sites, can be generated statically. Serving pre-rendered HTML is faster and more reliable than serving dynamically rendered content. Wikipedia lists some of the advantages that static web sites provide:

  • Improved security over dynamic websites (dynamic websites are at risk to web shell attacks if a vulnerability is present)
  • Improved performance for end users compared to dynamic websites
  • Fewer or no dependencies on systems such as databases or other application servers

Of course, some web sites (particularly web applications) will always require dynamic content. Even LWN, where the article contents are static, needs to be able to serve different content for logged-in subscribers and non-logged-in visitors. So there will always be a need for dynamic servers and APIs. But when a site’s content is static and public, or when any dynamic content can be handled on the client side, a static-site generator can significantly simplify the system.

Because static-site generators are relatively easy to build, there are many open-source options available. All of the major programming languages have fairly popular offerings, for example: Jekyll in Ruby, Pelican in Python, JBake in Java, Sculpin in PHP, Hexo in JavaScript, as well as hundreds of others. Here we look at Hugo, which is an active and well-maintained open-source project.

Hugo was created in 2013 by Steve Francia (now a member of the core Go team), but Bjørn Erik Pedersen took over as the lead developer in 2015. The project is hosted on GitHub; it is currently 43,000 lines of Go source (excluding comments) licensed under the permissive Apache License 2.0. It has a thorough documentation site built, of course, using Hugo. The project also has plenty of involvement from the community, with a relatively high-traffic discussion forum and a large number of user-submitted themes. Pedersen said in 2017 that Hugo’s infrastructure (but not development) has some corporate sponsorship, “Netlify hosts our sites for free and Discourse keeps the forum running. Travis, Appveyor and CircleCI for builds. But other than that, no sponsors.

It doesn’t matter too much what programming language a site generator is written in (I personally use Jekyll but have never needed to write Ruby), though developers often lean toward a tool that’s written in a language they’re familiar with. Even if a developer uses Hugo, they will probably never have to write Go, though if they are building or customizing a theme, they may need to dip into Go’s HTML templating syntax.

According to BuiltWith, Hugo usage has grown sharply over the last three years. It is used by several notable projects, including the redesigned Smashing Magazine site, the 1Password Support site and blog, the Let’s Encrypt web site, as well as the US government’s Digital.gov site.

How it works

When run without options, hugo traverses the Markdown files in the content subdirectory and outputs rendered HTML to a subdirectory named public. All of this is configurable — the default config file is config.toml in the root of the project. Output is rendered according to a theme, which is a handful of Go HTML templates that define exactly what HTML is generated. The theme includes CSS styling as well as any images or JavaScript files needed for the theme.

Before writing this article, I set up a simple test site with three articles and an “About” page — the kind of thing one might use for a personal web site or blog. It took about 30 minutes to choose a theme, configure Hugo, and add some dummy content. Hugo can be installed from popular operating system package managers, pre-built binaries on GitHub (for Linux, various BSDs, macOS, and Windows), or built from source. For Hugo, building from source is as simple as installing Go (if it is not already installed), cloning the project, and typing go build. On my machine it took about 20 seconds from clone to running “hugo version“.

During development, typing “hugo server” starts a local web server to serve the rendered content from RAM. In this mode, Hugo automatically watches the files and rebuilds the HTML as needed. There’s even some JavaScript in the rendered page to automatically reload it in the web browser as soon as that piece of content is changed (using LiveReload with a web socket). Hugo’s built-in server is based on the production-ready net/http server, but it’s usually simpler for deployment to pre-build and upload the rendered files to a static-site host such as Amazon S3, or serve them behind a regular web server such as NGINX.

Like other static-site generators, Hugo uses a simple file format for pages that consists of “front matter” in YAML format, separated from the Markdown content by “---” lines. Here is the content for the first post of my example site:

    ---
    title: "First Post"
    date: 2020-07-06T09:33:48+12:00
    categories:
    - Development
    ---

    The quick brown fox jumps over the lazy dog. Ee equals em cee squared.
    Let's try a [link](https://benhoyt.com/) and **some bold text**.

    Another paragraph. Hugo seems to be working. Writing Markdown is nice.

To give an idea of what a Hugo theme template looks like, here’s the single.html template that’s part of the Soho theme I used in my test (it renders a single blog article):

    {{ define "main" -}}
    <div class="post">
      <h1>{{ .Title }}</h1>

      {{ if ne .Type "page" -}}
      <div class="post-date">
        <time datetime="{{ .Date.Format "2006-01-02T15:04:05Z0700" }}">
            {{ .Date.Format "Jan 2, 2006" }}
        </time>
        &middot; {{ .ReadingTime }} min read
      </div>
      {{- end }}

      {{ .Content }}
    </div>
    {{- end }}

Hugo supports “taxonomies”, which are different ways of categorizing pages, and for each taxonomy it creates a page that lists all the articles with that taxonomy assigned. By default it creates “tags” and “categories” taxonomies, for example the “Development” category on my test site. The documentation shows various ways of ordering taxonomies or adding metadata to them.

The tool supports various ways to control page URLs (“permalinks”) in the rendered content, with the default of /:section/:filename/ producing URLs like /posts/third-article/. The :section name comes from the subdirectory name in content (e.g. posts), while :filename is the name of the file where the content came from without any extension. These “directory URLs” are done by creating the rendered HTML file in a directory with an index file: /posts/third-article/index.html.

One of the more advanced features of Hugo is what it calls “shortcodes“, which are small snippets of HTML (with parameters) that can be used to augment Markdown’s limited functionality without falling back to HTML. There are many builtin shortcodes, for example gist (to include a GitHub Gist), highlight (syntax-highlighted code), and youtube (an inline YouTube video). For example, the following shortcode:

    {{< youtube w7Ft2ymGmfc >}}

will produce this HTML output:

    <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      https://www.youtube.com/embed/w7Ft2ymGmfc?autoplay=1
    </div>

Hugo also includes more advanced features such as generating a table of contents from the headings within an article, alternative markup formats (Markdown is the default), a pipeline for pre-processing CSS and JavaScript, and image processing commands to resize and crop images.

Hugo is billed as being fast, with its home page saying “at <1 ms per page, the average site builds in less than a second“. Smashing Magazine’s 7500-page site apparently built in around 13 seconds. This speed is due in part to it being written in Go, a language that compiles to native binaries (rather than a bytecode-interpreted language like Python), but also partly due to various benchmarking and performance efforts that contributors have made over the years; for example, see the release notes for version 0.63 released in January 2020.

In general with static-site generators, content authors need to be developers, or at least be familiar with using a text editor, Markdown, and Git. However, web host Netlify has an open-source tool called Netlify CMS that allows non-developers to contribute using a WYSIWYG editor — the tool commits to a Git repository behind the scenes. Hugo’s documentation has a list of other similar tools.

Hosting options

With static-site generation, developers can choose to host the rendered output on any host or web server. Popular options are GitHub Pages, Amazon S3, Netlify, or self-hosting using the Caddy or NGINX web servers. Hugo has a documentation page with detailed setup information for various options.

For my test site, I decided to use Amazon S3 (with AWS’s Cloudfront CDN in front of it). This is pretty simple to set up, and it will handle many millions of page views for just a few dollars per month. You can of course avoid the cloud entirely and host on whatever server you have around — even a cheap server will be able to serve thousands of static file requests per second.

It would be straightforward to use continuous integration (for example, GitHub Actions) to run a Hugo build on every commit to a site’s Git repository, ensuring the rendered version is up to date at all times.

Wrapping up

Hugo doesn’t seem to have a public roadmap or fixed release schedule; in the last couple of years there has been a release about every month, with bug-fix releases in between. In July 2018, Pedersen talked about “the road to 1.0”, but right now the maintainers seem to be quite content with 0.x version numbers. The release names are often light-hearted, with a “Christmas Edition” and a “40K GitHub Stars Edition“.

Static-site generators are a relatively simple way to build and deploy web sites with mostly static content. Hugo’s speed, permissive license, and array of features make it an attractive choice for developers, whether it’s for a small personal web site or a large content-heavy one. All in all, Hugo is an active project that is worth keeping an eye on.

[We would like to thank Jim Garrison, who suggested this topic.]

Index entries for this article
GuestArticles Hoyt, Ben