Table of Contents
While undergoing my website rebuild, I experimented with implementing two standards from Schema.org: MusicRecording and Recipe. While I decided against including the former, I think explaining how they can be used within a Hugo setup may be helpful for others, so I have attempted a little tutorial here trying to walkthrough how I got the MusicRecording Schema integrated with Hugo, and then show the working code I have for the Recipe standard.
Why Schema.org?
The schema creates a form of structured data which is understandable by machines, and so for example, recipes can be rendered in other applications or web pages as they can understand the standard, as opposed to inputting the same information outside of the standard and not being able to be included via rich snippets or populating special sections of search engine results.
MusicRecording
For me, I wanted to display a few aspects:
- Artist Name
- Album Title
- Genre
- Release Date
Doing this requires three parts:
- Inputting the information we want to include.
- Making this information understandable for machines.
- Making this information visible for readers.
Front Matter
This begins by setting parameters within the post’s front matter:
---
title: "Album Title"
artist: "Artist Name"
release_date: YYYY-MM-DD
genre: "Genre"
music: true
---
I have also added music: true
which will be utilised later to tell Hugo if it should render the following JavaScript in a particular post.
JavaScript
Next, we need to get the Schema working:
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "MusicAlbum",
"name": "{{ .Title }}",
"byArtist": {
"@type": "MusicGroup",
"name": "{{ .Params.artist }}"
},
"datePublished": "{{ .Date.Format "2006-01-02" }}",
"description": "{{ .Params.description }}",
"genre": "{{ .Params.genre }}",
"releaseDate": "{{ .Params.release_date }}",
}
</script>
As you can see, I’m using the parameters set in the front matter to populate the JavaScript and the Schema standard through the curly brackets.
single.html
The single.html
file determines how your posts will be rendered on your Hugo website. You will need to modify it to add the Schema information in, and to render the front matter into the post, if desired.
You can create a .js file and then have your single.html
import it in, but I had huge issues trying to get this to work, so I decided to include the script directly in the file, as so:
{{ if isset .Params "music" }}
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "MusicAlbum",
"name": "{{ .Title }}",
"byArtist": {
"@type": "MusicGroup",
"name": "{{ .Params.artist }}"
},
"datePublished": "{{ .Date.Format "2006-01-02" }}",
"description": "{{ .Params.description }}",
"genre": "{{ .Params.genre }}",
"releaseDate": "{{ .Params.release_date }}"
}
</script>
{{ end }}
Through {{ if isset .Params "music" }}
, I set it so if the post has the parameter of “music” set to “true”, then it would load the script. If “music” is not set to “true”, the code will not load.
Rendering
We can modify the single.html
further thought to display the parameters we set for the reader to see. This is more of a HTML/CSS thing, and depends on how you want your website to look, but here is how I setup mine quite basically:
{{ if isset .Params "music" }}
<div class="music">
<span class="artist">{{ .Params.artist }}</span>
<ul>
<li>Released: {{ .Params.release_date | dateFormat "2006" }}</li>
<li>Genres: {{ .Params.genre }}</li>
<li>Rating: {{ .Params.rating }}</li>
</ul>
</div>
{{ end }}
As you can see, I used multiple curly brackets, relating back to the parameters previously set, into HTML, but again, only if the “music” parameter is set to “true”.
The end result is that I can type out a review of a music album as normal in any markdown editor, and it renders underneath this custom HTML within the {{ .Content }}
section in the single.html
file.
Recipe
The process is quite similar again for Recipe, and I assume for any other standards found on the Schema website, but I will quickly run through my Recipe setup that is being used currently on the website, which includes the following pieces of information:
- Ingredients
- Instructions
- Yield
- Description
I also wanted to include difficulty and a rough time, which I did, but these are outside of the standard and so would not be understood by machines.
I should also add that the Recipe standard is extensive, and if you wish to run a fully featured recipe website with precise prepare, cook, and total times, as well as provide nutritional information, I recommend adapting the following code to include these aspects.
Front Matter
Combining my own custom parameters with those applicable to the Schema standard, the relevant front matter for my Peanut Butter Cookie recipe looks like this:
---
title: "Peanut Butter Cookies"
recipe: true
description: "This very simple recipe makes thirty-six small cookies!"
ingredients:
- "150g Vegetable Margarine"
- "100g Smooth Peanut Butter"
- "200g Soft Brown Sugar"
- "200g Plain Flour"
instructions:
- "Step 1: Preheat to 180° Celsius in a conventional oven, or 160° Celsius in a fan oven."
- "Step 2: Mix everything together in a bowl, forming a dough."
- "Step 3: Create thirty-six small balls and place them on prepared baking trays."
- "Step 4: Bake for around eleven minutes or until golden brown."
- "Step 5: They will still be soft when you take them out of the oven, so leave them to rest for a few minutes as they harden up before eating."
recipeYield: "Thirty-Six Small Cookies"
difficulty: "Simple"
time: "Short"
---
title
is standard to Hugo, difficulty
and time
are custom made by me, and recipe: true
is used later to determine if the corresponding JavaScript will be loaded, but all other parameters here fit Schema’s Recipe standard, and the entire recipe is entered via the front matter.
JavaScript
The JavaScript in my single.html
file looks like this:
{{ if isset .Params "recipe" }}
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Recipe",
"name": "{{ .Title }}",
"description": "{{ .Params.description }}",
"recipeIngredient": {{ .Params.ingredients | jsonify }},
"recipeInstructions": {{ .Params.instructions | jsonify }},
"recipeYield": "{{ .Params.recipeYield }}"
}
}
</script>
{{ end }}
Rendering
And here is the relevant HTML in the single.html
file to render the parameters:
{{ if isset .Params "recipe" }}
<div class="recipe">
<ul>
<li>Yield: {{ .Params.recipeYield }}</li>
<li>Difficulty: {{ .Params.difficulty }}</li>
<li>Time: {{ .Params.time }}</li>
</ul>
</div>
{{ end }}
[...]
{{ if isset .Params "recipe" }}
<blockquote>{{ .Params.description }}</blockquote>
<div class="ingredients">
<h4>Ingredients:</h4>
<ul>
{{ with .Params.ingredients }}
{{ range . }}
<li>{{ . }}</li>
{{ end }}
{{ end }}
</ul>
</div>
<div class="instructions">
<ul>
{{ with .Params.instructions }}
{{ range . }}
<li>{{ . }}</li>
{{ end }}
{{ end }}
</ul>
</div>
{{ end }}