Preface
Content is not just for human that using web browser, but also for machine that read this page, such as search engine, or social media that read your opengraph.
Goal: Flexible SEO with Hexo, w3c, opengraph and twitter.
Don’t you love a preview on your site? I mean, that you can customize differently, based on the content.
You can see at above figure, each post has different image.
Source Code
You can download the source code of this article here.
Extract and run on CLI using $ npm install.
1: Prepare
There are few artefacts changes.
Artefacts
Edit
-
_config.yml.
-
themes/tutor-05/layout/site/head.ejs.
New
-
themes/tutor-05/layout/meta/html.ejs.
-
themes/tutor-05/layout/meta/seo.ejs.
-
themes/tutor-05/layout/meta/opengraph.ejs.
-
themes/tutor-05/layout/meta/twitter.ejs.
Example Frontmatter
- source/_posts/lyrics/moody-blues-white-satin.md.
2: Refactoring: Head Tag
Our first attempt is simple, consider move the meta tag to different template.
Skeleton
To avoid complicated code, we are going to us partial for meta tags. This including opengraph and twitter.
<%
// embedded javascript code here
...
%>
<head>
<%- partial('meta/html') %>
...
<%- partial('meta/seo', {description: description}) %>
<%- partial('meta/opengraph', {title: title, description: description}) %>
<%- partial('meta/twitter', {title: title, description: description}) %>
</head>
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">
<!--w3c-->
<meta name="description" content="Letters I’ve written, never meaning to send">
<meta name="keywords" content="lyric, pop, 60s, poetic, tragedy, truth, love">
<meta name="author" content="Mataharani">
<!--opengraph-->
<meta property="og:locale" content="en">
<meta property="og:title" content="Moody Blues - Nights in White Satin">
<meta property="og:type" content="article">
<meta property="og:description" content="Letters I’ve written, never meaning to send">
<meta property="og:url" content="http://example/2014/03/15/lyrics/moody-blues-white-satin/">
<meta property="og:site_name" content="Heartbeats for Bulma">
<meta property="og:image" content="http://example/site/images/adverts/one-page.png">
<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">
<!--twitter-->
<meta name="twitter:title" content="Moody Blues - Nights in White Satin">
<meta name="twitter:description" content="Letters I’ve written, never meaning to send">
...
</head>
You can also optimize twitter card if you want.
Javascript: EJS Head
Remember the javascript part, from last tutorial.
var title = page.title;
if (is_archive()){
title = __('archive');
if (is_month()){
title += ': ' + page.year + '/' + page.month;
} else if (is_year()){
title += ': ' + page.year;
}
} else if (is_category()){
title = __('category') + ': ' + page.category;
} else if (is_tag()){
title = __('tag') + ': ' + page.tag;
}
This is just a reminder of the last javascript that we use__.__ We still need this javascript.
Javascript: Description
Have a look at partial calls, all three partial takes description as paramater.
<%- partial('meta/seo', {description: description}) %>
<%- partial('meta/opengraph', {title: title, description: description}) %>
<%- partial('meta/twitter', {title: title, description: description}) %>
This description will be set by embedded javascript. Now add this embedded javascript.
var description = '';
if (page.description) {
description = page.description;
} else if (page.excerpt) {
description = page.excerpt
.replace(/<[^>]*>?/gm, ' ')
.substring(0, 155)
.trim();
}
We need to limit the length of the description, and alow remove trailing white space.
3: Meta HTML
Consider move important meta to special meta-html.html. This should be an easy task, since it is only HTML.
Layout: EJS Head
-
- themes/tutor-05/layout/site/head.ejs
- gitlab.com/…/layout/site/head.ejs
<%
...
%>
<head>
<%- partial('meta/html') %>
<title><%= title || config.title %></title>
<% if (theme.favicon){ %>
<link href="<%- theme.favicon %>" rel="shortcut icon" type="image/x-icon" />
<% } %>
<%- css(['/css/bulma.css', '/css/fontawesome.css', '/css/main.css']) %>
</head>
Notice this partial(‘meta/html’) code.
Layout: EJS Meta HTML
Remember that, some of these metas must come first in the head tag.
-
- themes/tutor-05/layout/meta/html.ejs
- gitlab.com/…/layout/meta/html.ejs
<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">
Or better, we can add more meta html tag.
<%/* Let's Fake the Generator */%>
<meta name="generator" content="Karimata 1.2.3 Media Engine" />
<%/* Chrome, Firefox OS and Opera */%>
<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 do make keywords is to utilize built in tags and categories in frontmatter. For example in a page we can set the frontmatter as below:
tags : [pop, 60s]
category : [lyric]
This will result as below:
<meta name="keywords" content="lyric, pop, 60s">
If these two are not enough we can add keywords field in frontmatter. So we can have more keywords without ruining, tags and categories in page. For example:
tags : [pop, 60s]
category : [lyric]
keywords : [poetic, tragedy, truth, love]
These three frontmatter setting will result as below:
<meta name="keywords" content="lyric, pop, 60s, poetic, tragedy, truth, love">
Layout: EJS Meta SEO
And here the powerful embedded javascript. It is very easy to do array manipulation using embedded javascript.
-
- themes/tutor-05/layout/meta/seo.ejs
- gitlab.com/…/layout/meta/seo.ejs
<%
var keywords = '';
var terms = [];
if (page.categories) {
page.categories.each(function(item){
terms.push(item.name);
})
}
if (page.tags) {
page.tags.each(function(item){
terms.push(item.name);
})
}
if (page.keywords) {
page.keywords.forEach(function(keyword){
terms.push(keyword);
})
}
if (terms) {
keywords = terms.join(', ');
}
%>
<meta name="description"
content="<%= description || config.description %>">
<meta name="keywords"
content="<%= keywords || config.keywords %>">
<meta name="author"
content="<%= page.author || config.author %>">
Just be aware, that the keywords is using forEach, while tags and categories using each. The differences is because keywords is simply array, while the other two processed internally by Hexo.
How Does Keywords Works?
Merge array into one string require join function.
if (terms) {
keywords = terms.join(', ');
}
Content: Example
---
layout : page
title : Moody Blues - Nights in White Satin
date : 2014/03/15 07:35:05
tags : [pop, 60s]
category : [lyric]
keywords : [poetic, tragedy, truth, love]
---
Letters I've written, never meaning to send
<!-- more -->
Nights in white satin, never reaching the end,
Letters I've written, never meaning to send
Beauty I've always missed, with these eyes before
Just what the truth is, I can't say anymore
'Cause I love you
Yes, I love you
Oh, I love you
This will result as below:
<meta name="description" content="Letters I’ve written, never meaning to send">
<meta name="keywords" content="lyric, pop, 60s, poetic, tragedy, truth, love">
<meta name="author" content="Mataharani">
5: 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:title" content="Moody Blues - Nights in White Satin">
<meta property="og:description" content="Letters I’ve written, never meaning to send">
<meta property="og:url" content="http://example/2014/03/15/lyrics/moody-blues-white-satin/">
<meta property="og:site_name" content="Heartbeats for Bulma">
...
<meta property="og:image" content="http://example/site/images/adverts/one-page.png">
...
<meta property="og:locality" content="Jakarta">
...
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
Javascript: 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.
var ogi;
if (page.opengraphimage) {
ogi = page.opengraphimage;
} else if(config.opengraphimage) {
ogi = config.opengraphimage;
} else {
ogi = 'images/broken-person.png';
}
ogi = config.url + url_for(ogi);
Now we have three possibility
-
Set in frontmatter.
-
General: set in _config.yml
-
Fallback: hardcoded as images/broken-person.png
You can use any image, whatever you like, rather than just hardcoded example.
Content: Frontmatter Example
title : Moody Blues - Nights in White Satin
date : 2014/03/15 07:35:05
opengraphimage: site/images/adverts/one-page.png
This will result as below:
<meta property="og:image" content="http://example/site/images/adverts/one-page.png">
Config: YAML Example
We can set parameters in configuration, as below:
-
- _config.yml
- gitlab.com/…/_config.yml
# Custom
opengraphimage : "images/logo-gear-opengraph.png"
Theme Configuration
To avoid hardcoded layout, theme can also have its own configuration as shown below:
-
- themes/tutor-05/_config.yml
- gitlab.com/…/themes/tutor-05/_config.yml
# Opengraph
og_latitude : "-6.193665"
og_longitude : "106.848558"
og_locality : Jakarta
og_country_name : Indonesia
The value in this theme configuration can be called in template.
Layout: EJS Meta Opengraph
Now the view part.
-
- themes/tutor-05/layout/meta/opengraph.ejs
- gitlab.com/…/layout/meta/opengraph.ejs
<%
...
%>
<meta property="og:locale"
content="<%= config.language || "en" %>">
<meta property="og:title"
content="<%= title || config.title %>">
<% if (is_post()) {%>
<meta property="og:type"
content="article">
<% } %>
<% if (page.excerpt) {%>
<meta property="og:description"
content="<%= description || config.description %>">
<% } %>
<meta property="og:url" content="<%= page.permalink %>">
<meta property="og:site_name" content="<%= config.title %>">
<meta property="og:image" content="<%= ogi %>">
<meta property="og:latitude" content="<%= theme.og_latitude %>"/>
<meta property="og:longitude" content="<%= theme.og_longitude %>"/>
<meta property="og:locality" content="<%= theme.og_locality %>"/>
<meta property="og:country-name" content="<%= theme.og_country_name %>"/>
6: SEO: Twitter
The twitter version is much more simple. Because, I do not really pay attention to twitter.
-
- themes/tutor-05/layout/meta/twitter.ejs
- gitlab.com/…/layout/meta/twitter.ejs
<meta name="twitter:title"
content="<%= title || config.title %>">
<% if (page.excerpt) {%>
<meta name="twitter:description"
content="<%= description || config.description %>">
<% } %>
<% if (site.data.owner) {%>
<meta name="twitter:site"
content="@<%= site.data.owner.twitter %>">
<% } %>
I have an example using Jekyll, if you need example about twitter card.
7: 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.
What is Next ?
Consider continue reading [ Netlify - Hosting ]. There are, some interesting topic about, how to make your blog site live.
Thank you for reading.