Implementing Structured Data (Schema) Carousel for Category pages in Hugo

On websites that I tend to create, I always try to utilise Structured Data (Schema) as much as possible. This invisible for ordinary visitor data is served in the background and is used by search engines and other websites for better positioning of your content.

On YummyRecipes.uk I have already widely implemented Schema for Recipes but would like to do some more.

Our categories pages (despite, on purpose, not having /category/ in its URL) present various recipes grouped adequately. As categories, like /tag/ pages, they suffer from exclusion from search engines. Fully understand that as, for example, one recipe can generate 10 or more tag pages, however, category pages are a slightly different part of the story.

I have been looking into Google Search Central documentation and found something called Carousel. Despite that, I already saw this before, but never really go into depth with that.

Reading how to add structured data I read through two examples of use cases – Summary page and A single page.

The summary page has a short description of each item in the list. Each description points to a separate details page that is focused entirely on one item.

A single, the all-in-one-page list contains all carousel information, including the full text of each item. For example, a list of the top movies in 2020, all contained on one page. This page doesn’t link out to other detail pages.

The summary page best matches with category pages used on Yummy Recipes, for example, category /dessert/.

The carousel for the summary page requires, that each details page (which is just a full recipe page) will have required structured data for Recipes. I got this already implemented.

In that case, each category page will only require a simplified carousel schema for summary and the magic will happen.

When you read for the first time about Carousel you may be a little overwhelmed when you will go into details page. It’s not. After you sorted out the Schema for Recipes the hard part is done. The Carousel is a simple addition!

The carousel only contains two dynamic parts. A position is a simple counter (1, 2, 3…) that matches the order of your list on a category page. The URL is a link to a details page, which is a link to a full recipe with implemented recipe schema.

I surprised myself with how crazy easy it is, so let’s use it.

First, we need to borrow a range that is used to display the content of the category page.

To customise how the category list will look like I created layout/category/category.html file.

Will not bring the whole file here, just the important bit.

{{ range where .Paginator.Pages "Params.hidden" "ne" true }}
<div class="title">
  <a class="recipe-title" href="{{ .Permalink }}">{{ .Title }}</a>
</div>

<!-- some other parts -->

{{ end }}

{{ partial "pagination.html" . }}  

As you see, we got already, inside the range link to our recipe which will be used in our URL part of the schema.

We just need to sort out position element.

Paginator used will make sure that it will display the only number of posts (recipes) as specified in config.toml in paginate = 16 part.

In that case, on other pages (/page/2/) the position need to reset itself and start counting once again from 1 (up to 16).

Hugo community on Discourse already provided a solution for me in Adding a counter to Range post, which I would like to reuse.

<!-- outside the range -->
{{ $counter := 0 }}
<!-- inside the range -->
{{ $counter = add $counter 1 }}
{{ $counter }}

I took the initial example from Google and worked to adopt it.

<script type="application/ld+json">
    {
      "@context":"https://schema.org",
      "@type":"ItemList",
      "itemListElement":[
        {
          "@type":"ListItem",
          "position":1,
          "url":"http://example.com/peanut-butter-cookies.html"
        },
        {
          "@type":"ListItem",
          "position":2,
          "url":"http://example.com/triple-chocolate-chunk.html"
        },
        {
          "@type":"ListItem",
          "position":3,
          "url":"http://example.com/snickerdoodles.html"
        }
      ]
    }
</script>

So it will (with help1) looks as follow:

<script type="application/ld+json">
    {
      "@context":"https://schema.org",
      "@type":"ItemList",
      "itemListElement":[
      {{ range $counter, $_ := where .Paginator.Pages "Params.hidden" "ne" true }}{{ if $counter }},{{ end }}
        {
          "@type":"ListItem",
          "position": {{ add $counter 1 }},
          "url": {{ .Permalink }}
        }
	    {{ end }}
      ]
    }
</script>

Having implemented Carousel Schema and (previously) Recipe Schema on linked pages, the output shall look like one from examples on Carousel Search Results documentation page.

Example of Recipe host carousel in Google Search Results

To check, if our schema in the category page is displayed correctly, just need to put our URL through Rich Results Test.


  1. Thank for Joe Mooring from Hugo community for pointing into right direction↩︎

Comments