Preface
Goal: Flexible SEO with w3c, opengraph and twitter.
Source Code
This article use tutor-05 theme. We will create it step by step.
1: Prepare
Content is not just for human that using web browser,
but also for machine that read this page, such as search engine.
You can enhanced your SEO,
using metadata already provided in frontmatter,
such as tags
and categories
.
Default Plugin
Jekyll has default plugin for SEO.
If you want SEO quickly, you can skip this article.
Social Media
Don’t you love a preview on your site? I mean, that you can customize differently, based on the content. SEO is a whole huge topic, and deserve their own article series. This article only cover the most common meta tag for your content.
You can see at above figure, each post has different image.
Description
In most SEO, either W3C, opengraph, and twitter, can have optionally have description. So first we are going to find out how to make description. One thing in my mind is to create excerpt in frontmatter in each page, but we should have fallback, just in case we forget to write excerpt.
{% capture spaceless %}
<!-- calculate description -->
{% if page.excerpt %}
{% assign description = page.excerpt | strip_html %}
{% else %}
{% assign description = site.description | strip %}
{% endif %}
{% assign description = description
| newline_to_br
| strip_newlines
| replace: '<br />', ' '
| strip_html
| strip
| truncatewords: 150 %}
{% endcapture %}
This description is optional.
2: Refactoring: Head Tag
My first attempt is simple,
consider move the meta tag
to different template.
Artefacts
There will be few artefacts changes.
I refactor site/head.html
into two partials:
-
meta/html.html
, -
meta/seo.html
,
Skeleton
To avoid complicated code, we are going to use partial for meta tags.
- [gitlab.com/…/_includes/head.html][tutor-vi-head]
<!-- calculate description -->
{% include meta/html.html %}
<title>{{ (page.title | default: site.title) | escape }}</title>
{% include meta/seo.html %}
Or in a complete manners.
{% include meta/html.html %}
<title>{{ (page.title | default: site.title) | escape }}</title>
{% include meta/seo.html %}
{% include meta/opengraph.html %}
{% include meta/twitter.html %}
We are going to use the first above first, to avoid complicated code.
SEO: HTML Preview
Don’t you love that your site is ready for Search Engine Optimization ? The HTML that we want to achieve is similar as example page below:
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible"
content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="generator" content="Karimata 1.2.3 Media Engine" />
<meta name="theme-color" content="#2980b9">
<title>Julien Baker - Something</title>
<meta name="author"
content="Julien Baker">
<meta name="keywords"
content="lyric, rock, 2010s, sad, emo, broken">
</head>
You can see them from either page info.
Or from view source page.
3: Meta HTML
Consider move important meta to special _includes/meta/html.html
.
This should be an easy task, since it is only HTML.
Partial: Liquid: Meta HTML
Remember that, some of these metas must come first in the head tag.
{% comment %}<!-- Required meta tags -->{% endcomment %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible"
content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% comment %}
The above 3 meta tags *must* come first in the head;
any other head content must come *after* these tags -->
{% endcomment %}
You can also add any metadata that you need, such as these lines below:
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
{% comment %} Let's Fake the Generator -->{% endcomment %}
<meta name="generator" content="Karimata 1.2.3 Media Engine" />
{% comment %} Chrome, Firefox OS and Opera -->{% endcomment %}
<meta name="theme-color" content="#2980b9">
4: SEO: W3C
The next step is, put valid w3c SEO related meta.
We have at least these three meta tags:
-
Description
-
Keywords
-
Author
Keywords
The easiest thing to make keywords is to utilize
built intags
and categories
in frontmatter,
and additionally a keywords
field, also in frontmatter.
For example in a page we can set the frontmatter as below:
---
layout : post
title : Julien Baker - Something
date : 2018-09-07 07:35:05 +0700
categories : lyric
tags : [rock, 2010s]
keywords : [sad, emo, broken]
author : Julien Baker
---
This will result as below:
<meta name="author"
content="Julien Baker">
<meta name="description"
content="I can't think of anyone, anyone else. I won't think of anyone else.">
<meta name="keywords"
content="lyric, rock, 2010s, sad, emo, broken">
Partial Liquid: Meta SEO
{% capture spaceless %}
<!-- keywords -->
{% assign cats = '' | split: '' %}
{% assign tags = '' | split: '' %}
{% assign words = '' | split: '' %}
{% assign terms = '' | split: '' %}
{% if page.categories %}
{% assign cats = page.categories %}
{% endif %}
{% if page.tags %}
{% assign tags = page.tags %}
{% endif %}
{% if page.keywords %}
{% assign words = page.keywords %}
{% endif %}
{% assign terms = cats | concat: tags | concat: words %}
{% assign termssize = terms | size %}
{% endcapture %}
<meta name="author"
content="{{ page.author | default: "epsi" }}">
<meta name="description"
content="{{ description }}">
{% if termssize != 0 %}
<meta name="keywords"
content="{{ terms | join: ", " }}">
{% endif %}
5: Plugin: Custom Filter
We can utilize liquid filter plugin, to calculate keywords. Just be aware that since not every CI/CD allow ruby plugin, we will not use this ruby plugin primarily.
Ruby Filter: TermKeywords
In ruby, the code is far shorter.
module Jekyll
module TermKeywords
def term_keywords(cats, tags, keywords)
[cats, tags, keywords].reject{ |array| array.nil? }.flatten
end
end
end
Liquid::Template.register_filter(Jekyll::TermKeywords)
Using Filter
Here comes the good parts, using the filter in partials.
{% capture spaceless %}
{% assign terms = page.categories
| term_keywords: page.tags
, page.keywords %}
{% assign terms_size = terms | size %}
{% endcapture %}
<meta name="author"
content="{{ page.author | default: "epsi" }}">
{% if terms_size != 0 %}
<meta name="keywords"
content="{{ terms | join: ", " }}">
{% endif %}
6: SEO: Opengraph
The Structure of opengraph can be read from official page:
HTML Preview
The HTML that we want to achieve is similar as below.
<meta property="og:locale" content="en_US">
<meta property="og:type" content="article">
<meta property="og:title" content="Julien Baker - Something">
<meta property="og:description" content="I can't think of anyone, anyone else. I won't think of anyone else.">
<meta property="og:url" content="http://localhost:4000/lyric/2018/09/07/julien-baker-something.html">
<meta property="og:site_name" content="Your mission. Good Luck!">
<meta property="og:image" content="https://t2.genius.com/unsafe/220x220/https%3A%2F%2Fimages.genius.com%2Fad35fd5b6e1e7808990ebcc93e8179b9.1000x1000x1.jpg">
<meta property="og:latitude" content="-6.193665"/>
<meta property="og:longitude" content="106.848558"/>
<meta property="og:locality" content="Jakarta"/>
<meta property="og:country-name" content="Indonesia"/>
I respect copyright, so instead of copy paste relevant image related to lyrics to my site, I grab original image URL from <genius.com>.
Structure
How to achieve ?
There are three parts:
-
Common properties: using conditional.
-
Image: using default image, depend on page type.
-
Location: set in theme configuration
Configuration: Opengraph Image
I put the default opengraph image in configuration.
#opengraph
locale: en_US
logo: /assets/images/logo-gear-opengraph.png
So if you forget to put opengraph image, in both page frontmatter and layout frontmatter, the page will use default image.
Layout: Post
So is the layout post
---
layout: default
aside_message : This is a post kind layout.
opengraph:
image: /assets/images/authors/broken-person.png
---
So if you forget to put opengraph image in page frontmatter, the page will use default image in post.
Liquid: Opengraph Image
To make life easier, consider separate code and view. Javascript in code, and html in view.
This would takes some conditional, but it self explanatory. If there is no frontmatter setting, then use sitewide configuration setting. If neither set, then fallback to default image.
{% capture spaceless %}
<!-- image -->
{% if page.opengraph.image %}
{% assign ogimage = page.opengraph.image %}
{% elsif layout.opengraph.image %}
{% assign ogimage = layout.opengraph.image %}
{% else %}
{% assign ogimage = site.logo %}
{% endif %}
<!-- fix -->
{% unless ogimage contains "http://" or ogimage contains "https://" %}
{% assign ogimage = ogimage | prepend: site.baseurl | prepend: site.url %}
{% endunless %}
{% assign ogtitle = (page.title | default: site.title) | escape %}
{% assign ogurl = page.url
| replace:'index.html',''
| prepend: site.baseurl
| prepend: site.url %}
{% endcapture %}
Now we have three possibility
-
Set in frontmatter.
-
General Post Image: set in
images/author/broken-person.png
. -
Fallback Image: hardcoded as
images/logo-gear-opengraph.png
.
You can use any image, whatever you like, rather than just hardcoded example.
HTML: Opengraph Image
Now we can have tidy HTML right below, that above liquid code.
{% comment %}<!-- Open Graph -->{% endcomment %}
<meta property="og:locale" content="{{ site.locale }}">
<meta property="og:type" content="article">
<meta property="og:title" content="{{ ogtitle }}">
<meta property="og:description" content="{{ description }}">
<meta property="og:url" content="{{ ogurl }}">
<meta property="og:site_name" content="{{ site.title }}">
<meta property="og:image" content="{{ ogimage }}">
<meta property="og:latitude" content="-6.193665"/>
<meta property="og:longitude" content="106.848558"/>
<meta property="og:locality" content="Jakarta"/>
<meta property="og:country-name" content="Indonesia"/>
Page Content: Frontmatter Example
layout : post
title : Julien Baker - Something
date : 2018-09-07 07:35:05 +0700
categories : lyric
tags : [rock, 2010s]
keywords : [sad, emo, broken]
author : Julien Baker
excerpt :
I can't think of anyone, anyone else.
I won't think of anyone else.
opengraph:
image: https://t2.genius.com/unsafe/220x220/https%3A%2F%2Fimages.genius.com%2Fad35fd5b6e1e7808990ebcc93e8179b9.1000x1000x1.jpg
This will result as below:
<meta property="og:locale" content="en_US">
<meta property="og:type" content="article">
<meta property="og:title" content="Julien Baker - Something">
<meta property="og:description" content="I can't think of anyone, anyone else. I won't think of anyone else.">
<meta property="og:url" content="http://localhost:4000/lyric/2018/09/07/julien-baker-something.html">
<meta property="og:site_name" content="Your mission. Good Luck!">
<meta property="og:image" content="https://t2.genius.com/unsafe/220x220/https%3A%2F%2Fimages.genius.com%2Fad35fd5b6e1e7808990ebcc93e8179b9.1000x1000x1.jpg">
7: SEO: Twitter
The twitter version is another challenge.
Partial: Liquid: Meta Twitter
{% comment %}
<!-- Twitter Cards -->
{% endcomment %}
<meta name="twitter:title" content="{{ (page.title | default: site.title) | escape }}">
<meta name="twitter:description" content="{{ description }}">
{% if site.owner.twitter %}
<meta name="twitter:site" content="@{{ site.owner.twitter }}">
{% endif %}
{% if author.twitter %}
<meta name="twitter:creator" content="@{{ author.twitter }}">
{% endif %}
{% if page.image.feature %}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ site.url }}{{ site.baseurl }}/images/{{ page.image.feature }}">
{% else %}
{% if page.image.thumb %}
{% assign twimg = site.baseurl | append: "/assets/images/" | append: page.image.thumb %}
{% else %}
{% assign twimg = site.baseurl | append: site.logo %}
{% endif %}
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="{{ site.url }}{{ twimg }}">
{% endif %}
With result similar to below:
<meta name="twitter:title" content="Julien Baker - Something">
<meta name="twitter:description" content="I can't think of anyone, anyone else. I won't think of anyone else.">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://localhost:4000/assets/images/logo-gear-opengraph.png">
I must admit, I do not really pay attention to twitter.
8: Preview: Social Media
How does it looks on social media? Enough with code, now see figure below.
Telegram on Smartphone
Slack on Smartphone
The thing is, every social media interpret differently. In fact smartphone view and desktop, show have different result of opengraph image, based on image size and screen width, as you can see in inkscape article above
Beyond this Article
Just remember that SEO specification is changing from time to time, and this article is only covering the basic SEO. Here below is a reference for a serious SEO coder.
Good luck with SEO!
What is Next ?
Consider continue reading [ Jekyll - Plain - Pagination - Intro ].
Thank you for reading.