Websites are read by more than just human eyes. Browsers, search engines, screen readers, and more take the information being served to them about a site and use it for different purposes. I wanted to make sure that my site could be parsed properly so that it can be reached to everyone who wants it. The web has several technologies designed for this purpose that do not add too much more work to incorporate them. I focused on Schema and OpenGraph meta tags in the <head>
element to achieve this. An excellent resource for learning about it can be found at HTML Head.
Preface
Hugo offers ways of doing this by having your template reference some internal templates. For example, the very popular template Ananke uses the following code to utilize these templates.
{{- template "_internal/opengraph.html" . -}}
{{- template "_internal/schema.html" . -}}
{{- template "_internal/twitter_cards.html" . -}}
At the beginning of this process, I found that including this would cause the site not to render on Micro.blog. That issue has since been resolved, but I kept with my code for my understanding and further customization.
The good thing about all of this is that Ananke has a link directly over the templates so that you can review the code yourself.
It can be found here: Embedded Templates
Another key difference is that the templates also have logic for a featured
image. In most of my usage, I don’t have images and don’t know how to specify an image as featured in markdown. I’ll have to reevaluate this at a later date.
Heading Home
When creating a template for blog, you should be aware that not all tags that you would have for your articles apply to your main page.
I used a flag that Hugo provides to create a section in the header for the home page to keep tags that a specific t the landing page.
For the individual articles, I’ll be checking the title property in to determine logic on what tags that will be used.
{{ if isset .Params "title" }}
When you read the article, you will see different times that I specified something and may think that I could have been more clever by writing it in a way where I don’t have to repeat myself. I did this for a few reasons.
- I’m learning how Hugo works with templates
- I find that clever code can be difficult to debug and understand (You can thank me later, future me)
Getting a head of the page
I wanted to start off with the <head>
elements that Micro.blog uses before checking to see if there were other things that I needed to add for the website.
I like to look at the Marfa’s head partial because it’s the theme that Manton uses.
{{ "<!-- Micro.blog Values -->" | safeHTML }}
<link rel="shortcut icon"
href="https://micro.blog/{{ .Site.Author.username }}/favicon.png"
type="image/x-icon" />
<link rel="me" href="https://micro.blog/{{ .Site.Author.username }}" />
<link rel="authorization_endpoint" href="https://micro.blog/indieauth/auth" />
<link rel="token_endpoint" href="https://micro.blog/indieauth/token" />
<link rel="micropub" href="https://micro.blog/micropub" />
<link rel="microsub" href="https://micro.blog/microsub" />
<link rel="webmention" href="https://micro.blog/webmention" />
<link rel="subscribe" href="https://micro.blog/users/follow" />
<link rel="canonical" href="{{ .Permalink }}" />
A lot of the these values are specific to how Micro.blog and the open web respond to the site in general. Blogging software such as MarsEdit, will use this information to connect to your blog for editing. Webmentions are a way for different systems to communicate to regarding if the source content has been referenced somewhere else.
{{ if .RSSLink -}}
<link href="{{ .RSSLink }}" rel="alternate" type="application/rss+xml" title="{{ .Site.Title }}" />
<link href="{{ "podcast.xml" | absURL }}" rel="alternate" type="application/rss+xml" title="Podcast" />
<link rel="alternate" type="application/json" title="{{ .Site.Title }}" href="{{ "feed.json" | absURL }}" />
<link rel="EditURI" type="application/rsd+xml" href="{{ "rsd.xml" | absURL }}" />
{{ end -}}
These allow for newsreaders and podcast aggregators to get the information needed to get information about updates. One cool thing is the use of a JSON feed that Manton helped build.
I also found a website dedicated to explaining a lot of the different tags that can be found in the <head>
element.
Open examples
The first set of tags that I looked at were ones related to OpenGraph tags. These where the ones that a supported in social media (facebook, twitter, slack, etc.) and operating systems that I was using at the beginning of the project.
OpenGraph tags behave the same way as <title>
and other tags in a web page. In fact, for some services, if an OpenGraph tag is not there, it will default to other tags in the head. For example, title or description. I decided to define the values for the tag to make sure that the site is compliant.
The key difference between OpenGraph tags and the standard tags that you see in the <head>
element of a website is that they are defined as <meta>
tags. A tag would have a property
and content
definition that would follow a pattern shown below.
<meta property="PropertyName" content="PropertyString" />
In the examples that I have seen, they often duplicate some of the information that is already there. In most cases, you would see:
<title>Some Title</title>
<meta property="og:title"
content="Some Title" />
This makes it fairly effortless to get most of the tags together with only a few complications with dates, images, and whether to support twitters extensions.
Dates
A long time ago, some smart people decided that there should be an international standard for representing dates. As of this writing, Hugo doesn’t automatically format the date to this standard.
{{ $iso8601 := "2006-01-02T15:04:05Z07:00" }}
This line defines a string that we can use to later format dates in other parts of the template.
{{- with .PublishDate }}
<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />
{{ end }}
Then when a page is rendered we can see the time and in which time zone.
<meta property="article:published_time" content="2023-03-23T16:50:58-07:00" />
Images
For the home page, we assume that the user wants to use their favicon for their image property.
<meta property="og:image" content="https://micro.blog/{{ .Site.Author.username }}/favicon.png" />
This works fine for home pages, but we want to provide more information for the articles that may have it.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{{- with .Params.images -}}
{{ "<!-- .Params.images -->" | safeHTML }}
<meta name="twitter:card" content="summary_large_image" />
{{- range first 6 . }}
<meta property="og:image" content="{{ . | absURL }}" />
<meta itemprop="image" content="{{ . | absURL }}" />
{{- end -}}
{{- else -}}
{{ "<!-- $.Site.Params.images -->" | safeHTML }}
<meta property="og:image" content="https://micro.blog/{{ .Site.Author.username }}/favicon.png" />
<meta itemprop="image" content="https://micro.blog/{{ .Site.Author.username }}/favicon.png" />
<meta name="twitter:card" content="summary" />
{{- end -}}
|
The first line checks that images exist in the article.
Line 2
creates a comment in the rendered HTML.
Line 3
is specific to twitter makes the images bigger in the timeline.
Line 4
sets up loop over an array. The key words first 6
limits the loop and the .
specifies the array as being the images defined in .Param.images
.
Line 5
creates the line of html for that particular image we are on in the loop.
Line 8
starts our logic when there are no images attached to the article and defaults to giving the user profile picture.
Modern sites can also have audio and visual components as well.
{{- with .Params.audio }}{{ range . }}<meta property="og:audio" content="{{ . }}" />{{ end }}{{ end }}
The line looks at all the audio in the article and then adds the following property.
<meta property="og:audio" content="https://micro.blog/pages/downloads/661/1841919/750047.mp3" />
You can also do the same looping code to go over any video that you might have.
{{- with .Params.videos }}{{- range . }}
<meta property="og:video" content="{{ . | absURL }}" />
{{ end }}{{ end }}
And it will render correctly.
<meta property="og:video" content="https://mandarismoore.com/uploads/2021/793e8ba1bb.mp4" />
At the moment, I don’t have an examples of a system processing this information, but I’m happy knowing that it’s there.
As I was working on this, Twitter was acquired by Elon Musk. There were numerous layoffs and I honestly don’t know if I should put in the time to make sure that I support the twitter specific extensions to the OpenGraph specs. Ultimately, I added them in case someone uses this theme and shares the links on Twitter.
Micro.blog allows a user to add their twitter name and I used that to populate both twitter:site
and twitter:creator
.
1
2
3
4
5
|
{{ with .Site.Params.twitter_username }}
<link rel="me" href="https://twitter.com/{{ . }}" />
<meta name="twitter:site" content="@{{ . }}" />
<meta name="twitter:creator" content="@{{ . }}" />
{{ end }}
|
If the user doesn’t have a twitter username, this code will not be included during the rendering of the website.
Scheming
The key difference between OpenGraph and Schema tags is that they use different tags to represent the same information.
A schema meta tag will usually look like the following.
<meta itemprop="PropertyName" content="PropertyString" />
This is basically the same idea but implemented by different groups. When I first learned of Schema tags, I was told that it was used by search engines to parse information. When I attempted to verify this, half the tools used to check have been shutdown or made reference to using OpenGraph.
Still, I wanted to include them as the logic was already completed during my implementation of OpenGraph tags.
In addition to the meta tags, you will see the schema tags in the article itself.
For example, in the <span>
tags of the article footers for the different dates and categories.
<span itemprop="articleSection">{{ $Category }}</span>
Currently, I’ve mixed in all the schema tags with the OpenGraph tags to make sure that I have parity between the two where it makes sense.
Evaluating
For open graph testing, I created a page on the test version of my site using the two following tools.
https://opengraphr.com/open-graph-debugger
https://www.opengraph.xyz/
Schema
I used https://validator.schema.org to test Schema.
Accessibility
I used https://wave.webaim.org to test for accessibility. I did get some alerts with the design because I had some video and audio tags but no transcripts. Another issue is the contrast that I used for the Permalinks on dates. I’ll work on that in a future release.
The twitter official validator is https://cards-dev.twitter.com/validator. NOTE Removed as of mid 2022. You have to post the link in the composer window of a tweet to see what it will look like.
Opportunities to grow
During the writing of this article, I found numerous ways that I can improve my theme. So much, that I had stopped development because I didn’t want the article and theme to become too separate from each other.
To continue with development without worrying about this, I’ve tagged the version of the code for the time that these steps of the article were created. You can find the link below.
Labarum 1.0.1
In the meantime, I’ll be cleaning up the code so that the Hugo Theme is easier for me to read and that the rendered output as well.
I tested the accessibility of the site, and it did have a decent score, but I’d really like to improve the fonts and colors.
You are welcome to follow this site or the Labarum category to see updates when this is updated. And you can always contact me here or via email!
Other posts in this series
Creating Labarum Part 1: Design and initialization
Creating Labarum Part 2: Creating the individual posts
Creating Labarum Part 3: Implementing post footer
Creating Labarum Part 4: Initiating the Index