Adding JSON-LD to a Hugo site
JSON-LD gives search engines structured information about a page without mixing that data into the visible HTML. For a blog, Schema.org’s WebSite and BlogPosting types are a useful place to start.
Structured data does not guarantee a ranking boost or a rich result. Its practical job is to describe the page clearly in a format search engines can process.
This is the Hugo template I used on my blog in 2020. Put it in a partial or include it directly between the <head> tags. It outputs site-level data on the home page and article data on regular pages.
{{ if .IsHome -}}
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "WebSite",
"name": "{{ .Site.Title }}",
"url": "{{ .Site.BaseURL }}",
"description": "{{ .Site.Params.description }}",
"thumbnailUrl": "{{ .Site.Params.Logo | absURL }}",
"license": "{{ .Site.Params.Copyright }}"
}
</script>
{{ else if .IsPage }}
{{ $author := or (.Params.author) (.Site.Author.name) }}
{{ $organization := .Site.Title }}
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "BlogPosting",
"articleSection": "{{ .Section }}",
"name": "{{ .Title | safeJS }}",
"headline": "{{ .Title | safeJS }}",
"alternativeHeadline": "{{ .Params.lead }}",
"description": "{{ if .Description }}{{ .Description | safeJS }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ end }}{{ end }}",
"inLanguage": "{{ .Site.LanguageCode | default "en-us" }}",
"isFamilyFriendly": "true",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ .Permalink }}"
},
"author" : {
"@type": "Person",
"name": "{{ $author }}"
},
"creator" : {
"@type": "Person",
"name": "{{ $author }}"
},
"accountablePerson" : {
"@type": "Person",
"name": "{{ $author }}"
},
"copyrightHolder" : "{{ $organization }}",
"copyrightYear" : "{{ .Date.Format "2006" }}",
"dateCreated": "{{ .Date.Format "2006-01-02T15:04:05.00Z" | safeHTML }}",
"datePublished": "{{ .PublishDate.Format "2006-01-02T15:04:05.00Z" | safeHTML }}",
"dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05.00Z" | safeHTML }}",
"publisher":{
"@type":"Organization",
"name": "{{ $organization }}",
"url": "{{ .Site.BaseURL }}",
"logo": {
"@type": "ImageObject",
"url": "{{ .Site.Params.logo | absURL }}",
"width":"32",
"height":"32"
}
},
"image": {{ if .Params.images }}[{{ range $i, $e := .Params.images }}{{ if $i }}, {{ end }}"{{ $e | absURL }}"{{ end }}]{{ else }}"{{ .Site.Params.logo | absURL }}"{{ end }},
"url" : "{{ .Permalink }}",
"wordCount" : "{{ .WordCount }}",
"genre" : [ {{ range $index, $tag := .Params.tags }}{{ if $index }}, {{ end }}"{{ $tag }}" {{ end }}],
"keywords" : [ {{ range $index, $tag := .Params.tags }}{{ if $index }}, {{ end }}"{{ $tag }}" {{ end }}]
}
</script>
{{ end }}
The template expects matching values in the site’s configuration:
baseURL = "https://ronaldsvilcins.com/"
languageCode = "en-us"
title = "Ronalds Vilcins"
[params]
description = "A design-minded full-stack developer and digital advertiser."
author = "Ronalds Vilcins"
copyright = "Ronalds Vilcins"
logo = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
Replace every example value, especially the base URL, author, copyright, and logo. I would also run the rendered page through a structured-data validator: template syntax can be valid while the resulting JSON still contains a missing quote or an empty field.