The modern JAMstack site and app architecture offers huge business advantages—speed, improved security, and robust reliability among them. For developers, too, it’s a breath of fresh air, providing a modular and flexible approach to work. It allows us to use the services, platforms, and methodologies that are most appropriate for the project at hand.

What is content modeling?

Content modeling is a process that inventories, assesses, documents, and describes all the content for a project. A content model describes how content is structured, sorted, and grouped into content types, how it relates to other content, and how it should interact with the user and perform over time. The content’s structure is a description of its characteristics and components—like titles, ledes, excerpts, dates, authors, calls to action, and more.

So, in a content model, we describe:

  1. The structure of the content—its components and attributes,
  2. The types of content—the sorts and groups that have similar characteristics, and
  3. The behavior or the business rules of the content, which describe how it relates to other content and how it performs.

Examples of content behavior include things like, “this product page should be visible in the new product section, but only for three weeks after its published dated, and then it should be moved to the on sale section 12 weeks afterward.”

How is content modeled in Hugo?

Hugo is incredibly flexible, and its content handling is clear and sensible. It supports content types through the use of paths that define sections — put each news article in the /news/ section, each event in the /events/ section. The content’s metadata, structure, and attributes are defined in front matter at the top of the content’s markdown file. For example:

+++
date = "2020-01-20"
draft = false
title = "Modular content modeling in the JAMstack framework with Forestry front matter templates and Hugo"
categories = ["Development", "JAMstack"]
description = "With Forestry's front matter templates, it's easy to create modular content models that govern both content structure and Hugo behaviors."
+++

Hugo offers a broad range of content features “out of the box” that are incredibly useful. Predefined front matter variables support behaviors like draft or published, sorting, publishing on a future date, specific URL paths, URL redirections, and many more.

How can we consistently model content behavior across different content types?

A challenge I’ve experienced is that Hugo’s vast flexibility can lead to a bit of laziness on behalf of the developer—not every feature is needed on every project, and it’s easy to forget that some useful features exist, or worse, to inconsistently apply behaviors to some content types and not others. After all, if I can create URL redirects for a landing page, why not create them for a product page or a news article as well? What’s needed is — of course, you see where I’m going here — a methodology to ensure like-types of behaviors are made easy-to-use for editors, and modular, so that they can be attached to different content types.

Handling Hugo’s URL behaviors with Forestry’s Front Matter Templates

When I first came across Forestry’s Front Matter Templates (FMTs) I didn’t fully understand how useful they can be. Of course, they define the editor’s content editing experience in Forestry and provide a broad range of field types — different kinds of content widgets to support dates, text, related content, and more. But thanks to a field type I initially overlooked—Include Template—FMTs become powerful tools for modular content modeling. Let’s see how to use them to modularize some of Hugo’s URL behaviors.

Hugo provides many powerful predefined front matter variables for URL management. For example, Aliases create redirects to a page from other URLs and take the form of an array of URL paths.

+++
aliases = [
	"/articles/old-article-title",
	"/old-website/old-path/preserve-that-inbound-link-SEO"
]
+++

Another variable, slug, enables editors to customize the URL. Typically, Hugo builds a content’s path from its filename, which is (in Forestry) created by its title. By specifying a slug, an editor can better control public-facing URL paths.

content/events/valentines-day-2020.md

+++
title = "Valentine's Day prosecco tasting at noon!"
slug = "vday-2020"
+++

It’s easy to define the slug in the Forestry FMT as a Text Field type:

Slug Definition

And since Aliases is an array of URLs, we’ll use Forestry’s Sortable List type (even though order isn’t important for this example).

To finish this up, let’s add a Toggle called URL features, and set both aliases and slug to be visible only when the switch is thrown:

Set Visibility

Finally, save this FMT as “c-urls” — the c- prefix is a flag for you that this FMT is for use as a modular content component in other templates. To add this component to another template, use the Include Template field type. You’ll see it in the dropdown list, nicely sorted with the c- prefix:

URL Component

In my example, I added it to a Post FMT:

Post Template

Thanks to that excellent URL features toggle, our editor has an uncluttered, focused view of her content.

Handling shared content structure with Forestry’s Front Matter Templates

If you saw the Call to Action switch in the example above, you can imagine what’s next. A call to action (CTA) might appear on a landing page, an article, or an event. Let’s make that consistent through a c-call-to-action component.

Version 1: a Call To Action component

For our example, let’s compose the CTA with a headline, supporting text, and a linked button. Add the Call to Action Toggle and set all the visibilities accordingly to create your c-cta component:

CTA v-1

This new c-cta component can be added as an included template to any content type you’d like. Here its been added to Posts:

FMT Post

Thanks to that Toggle, again editors get a great, focused experience:

CTA in Use

It’s up to the Hugo templates to render this; in the example code I set it up to display something simple:

CTA Rendered

Version 2: a Call To Action component, composed of sub-components

I was reading DJ Walker’s article 3 Tips for Mastering Blocks when the lightbulb of included templates went off for me: “I refactored the front matter templates so that three common field patterns were abstracted away into Front Matter Partial templates: Links (URL + link text + link target), Media Elements (image + caption), Text Columns (header + content).”

The Version 1 CTA component has a lot of complexity. Walker points us in the right direction. We can refactor v1: abstract away sub-components, like a button or structured text, that could likely be re-used elsewhere. To do this, let’s create the sub-components c-text-column and c-button, c-text-column which isolate the functionality of headline and support text, and button text and URL.

C-Button

Then, we’ll include these into our c-cta-v2 component:

CTA v-2

Note that Included Templates have a name (“text Column”, “Button”) but these are just for our own reference — they aren’t shown to editors or in the Forestry UI. This nested approach enables an atomic, modular approach to content modeling. Once version controlled, these sub-components benefit from incremental improvement over helper text found in field descriptions, too.

Naming is hard!

When I first started creating modular content models in Forestry, I confused myself with the template names I made. They are all templates, but which templates are components? And which are components of components? I’ve devised an initial naming scheme to help. I call content type templates—which may be assigned to Forestry’s sidebar sections—the content type name + “ template”, for example, Post Template. I call modular content components “c-“ + the feature or structured content name, for example, c-seo, c-url. While I’m currently calling sub-components with the c- prefix too, I might in future builds distinguish these as elements, with an “e-“ prefix: a nod to Brad Frost’s Atomic Design principles.

Demo repository: try it out yourself

I’ve setup a test code repository so you can try out these techniques yourself. What recommendations would you make for increased modularity?