Build Web Pages from JSON Data with Hugo

In this second look at building a website using Hugo, a static site generator, I’m going to explore how to assemble pages from structured data rather than written content.

One use-case for this is a release notes page in a site devoted to a software applications. Case in point: I wrote an app called Squinter which is a macOS tool for developing apps on the Electric Imp Platform. In addition to a page describing the software, I have a separate page which present the app’s release notes. Each note is prefixed with a lozenge indicating whether the note refers to a new feature, an enhancement to an existing feature, or is a bug fix.

Originally this page was a huge mass of HTML, and I had to cut and paste a block of existing release notes then edit the duplicated text into the notes for each new release. When I migrated my site to Hugo, I wondered if I could generate this page in a more joined-up, less laborious way.

I found I could and here’s how I did it.

Recall that each Hugo content source page is a markdown file with a metadata section — the ‘front matter’ – and the page content itself. Now you don’t actually need to include any content outside of the front matter, which is intended to contain data about the page. However, there’s nothing to prevent you from including structured content within the metadata, and that’s what I did.

Hugo’s front matter can be entered in YAML or TOML format, but in this case I chose the third option, JSON. The release notes file’s front matter contains a custom field, releases, which is an array of objects containing the fields version, date and notes. The latter is an array of objects with the fields type and text — respectively, the kind of note (new, improved or fixed) and the text of the note.

To detail a new release, I just add a new object to the releases array and fill in the details. At build time, Hugo creates a complete page for me.

So how does Hugo extract that information onto the release notes page? The front matter contains the field layout with the values squinter_releases. This tells Hugo to use the page template squinter_releases.html. Most of this file sets up the page around the release notes list — navbar, heading and so on — so we’ll ignore that and focus on the key code:

{{ if isset .Params "releases" }}
  {{ range .Params.releases }}
  <h3 class="h3under">{{ .version }} ({{ .date }})</h3>
  {{ if isset . "notes" }}
    <ul class="list-style-none change-log">
    {{ range $note := .notes }}
      <li class="d-flex flex-items-start mb-2">
        <div class="change-badge change-badge-{{ $note.type }}">{{ $note.type }}</div>
        <div class="change-description">{{ $note.text }}</div>
      </li>
    {{ end }}
    </ul>
  {{ end }}
  <p class="linktext" align="right"><a class="link" href="#ctop">Back to the Top</a></p>
  <hr />
  {{ end }}
{{ end }}

The first line checks that the front matter contains a field called releases. Remember, custom front matter fields are accessed through Hugo’s .Params value. If this field is present and has a value, we use use Hugo’s range function to iterate through all the elements in releases.

For each element, which describes a single Squinter release, the code extracts the version and date values into the HTML and then, if the notes field is present, we iterate through this array’s elements. This time we specify a variable, $note, to reference each element. Note the use of the Go language’s := initial assignment operator. Again, we extract values via the current element, using them to populate not only displayed text but also the HTML division class used to define each of the three types of lozenge.

Each code block using if or range is terminated with a {{ end }} marker.

When you run hugo server to test the site or hugo to build it, Hugo reads in the source markdown file (which doesn’t include any markdown in this case) and the specified layout file. It then runs the code in the template which extracts and assembles the release data. So the snippet above yields a page which details (currently) 12 Squinter releases.

I also use this technique to populate parts of my site’s home page: this lists of Electric Imp applications and libraries are built this way.

The home page’s other sections are set up using a similar approach, but this time Hugo is instructed to iterate through the site’s pages and if a given page contains a certain type of content — it describes a macOS app, an iOS app, a Swift library or a Python app — key values are extracted from the page’s front matter and used to populate part of the home page. Data extracted include the software’s name, version, when it was last updated, its description and the file name of its icon.

I also use Hugo to check for and flag recent app updates on the homepage, and I’ll show how that’s done next time.

More on Hugo…