Creating reusable content for static site generators (SSGs)
I modularized my content to display it in the right place at the right time—a major tenet of content strategy.
What is reusable content?
In a structured authoring environment, such as those that use Darwin Information Typing Architecture (DITA), an XML-based authoring language, you can set up content reuse, or snippets. Instead of copying and pasting sections into different files, you create a DITA file that contains that small piece of content. You reference that file where it’s needed, like an include or import file.
Some unstructured syntaxes and SSGs have their own implementations of reuse. However, this becomes an education and enforcement issue if you have a wide variety of contributors such as developers, product managers, subject matter experts, and technical writers. This also exposes your business to vendor lock-in, because once you have the toolset implemented, it’s hard to move away from it, especially when you come to rely on features other tools don’t have.
Metadata for content reuse
Copy-pasted content across pages is a maintenance nightmare. Someone has to remember to update it, and also all the places of the pasted content. With this in mind, I wanted to use metadata as much as possible for reuse and programmatic data access.
You can use metadata (also referred to as front matter or frontmatter) to define page titles, keywords, and leverage built-in categories and tags.
The real power comes by adding custom metadata: highly structured content that expands the possibilities of the template engine and site generator. For example, I used the description
field for the first sentence across the site. This allowed me to programmatically display the description on cards and each blog post, and let me style descriptions separately from everything else. It also enforces structure and allows writers and contributors to just write, since all of the logic is handled programmatically.
Most metadata fits a key-value pair format. Here’s what a metadata block looks like in YAML format:
---
title : 'The how: Building the site structure'
description : Putting all of the content pieces together.
tags : content-strategy
featured : true
featuredOrder : 3
FontAwesomeIcon : solid fa-file-waveform
---
Here’s what each metadata entry does:
Fieldname | Purpose |
---|---|
title |
Page title that displays on cards, grids, details pages, and browser tabs. Note that in this instance, I had to wrap the title in single quotes as there is a colon in it; this would break the page otherwise. |
description |
The first paragraph of the post. |
FontAwesomeIcon |
Specifies the Font Awesome icon to display on the homepage, grid pages, and breadcrumbs. |
featured |
Displays the page on a card on the homepage. |
featuredOrder |
Sets the order in which the card displays on the homepage. |
Here’s an example of how this content works programmatically. This code generates cards that references the metadata fields:
{%- macro gridItem(item) -%}
<div class="bg-whitish p-4">
<h3 aria-labelledby="{{ item.data.title |slugify }}">
{% if item.data.FontAwesomeIcon %}
<span class="fa-{{ item.data.FontAwesomeIcon }} text-2xl text-medium-blue"></span>
{% endif %}
{% if item.data.cover %}
<img src="/assets/images/{{ item.data.cover }}" alt="{{ item.data.coverAlt or item.data.title }}" data-pagefind-meta="image[{{ item.data.cover }}], image_alt[{{ item.data.coverAlt or item.data.title }}]" class="w-full h-48 object-cover mb-2">
{% endif %}
<a href="{{ item.url }}">{{ item.data.title | safe }}</a>
</h3>
<p>{{ item.data.description }}</p>
</div>
{% endmacro %}
Here’s what two cards look like side-by-side:
Auto-generated, context-sensitive links
The content stategy for my skills pages was to display the relevant tools and presentations for each skill. These started as bulleted lists, but it quickly became obvious that copying and pasting relevant bullets on each page wasn’t sustainable.
Since there’s no database behind a static site generator, I created two json
files that enforce structure while remaining expandable. The first file contained information about my presentations, webinars, and guest appearances. This json
file contained titles, year (or years) of the item, a relevant link, the category or categories for each, the location or event venue, and type (webinar, in-person, panel discussion, podcast guest or host, etc.). Here’s an example:
{
"title": "So, you want to be a technical writer?",
"year": "2024",
"link": "https://www.brighttalk.com/webcast/9273/608187",
"category": ["technical-writing", "metrics", "content-strategy", "marketing"],
"venue": "Content Wrangler webcast",
"type": "webinar"
},
The second json
file listed the tools I’ve used over my career, along with their publisher, product name, and relevant site categories:
{
"category": [
"technical-writing",
"help-authoring-tool"
],
"publisher": "MadCap",
"title": "Flare"
},
Then I created templates that render these structures, achieving a single source of truth that adapts to different display contexts. Here’s part of the template renders presentation data:
{% if presentations %}
<h2>Related work</h2>
{% set presentationsList = isPublicSpeakingPage and presentations or filteredPresentations %}
{% for presentation in presentationsList | sort(false, false, 'year') | reverse %}
<div class="presentation mb-4">
<h3 class="font-normal text-[1rem]">
{# Check if presentation has a link #}
{% if presentation.link %}
{{ presentation.type | capitalize }}:
<a href="{{ presentation.link }}" class="hover:text-success-600 italic">
{{ presentation.title }}
</a>
{% else %}
{{ presentation.title }}
{% endif %}
</h3>
<p class="text-sm mt-0 jet-600">
{{ presentation.venue }}, {{ presentation.year }}
</p>
</div>
{% endfor %}
{% endif %}
I can now programmatically populate my skills pages with the relevant presentations and tools: