ssg  
Where to Discuss?

Local Group

Preface

Goal: Flexible SEO with w3c, opengraph and twitter.

Source Code

This article use tutor-08 theme. We will create it step by step.


1: Prepare

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.

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.

11ty Meta: Telegram Desktop Opengraph

You can see at above figure, each post has different image.

Data: Adding More Metadata

Separate Data from Configuration

As I said previously, metadata tends to grow. We can put default data for our meta SEO in metadata.js.

module.exports = {
  "title" : "Your mission. Good Luck!",
  "author": "epsi",
  "email" : "epsi.nurwijayadi@gmail.com",
  "site"  : "https://eleventy-step.netlify.com",
  now     : new Date(),
  "opengraph": {
    "languageCode" : "en-us",
    "image"        : "/assets/images/logo-gear-opengraph.png",
    "postimage"    : "/assets/images/authors/broken-person.png",
    "latitude"     : "-6.193665",
    "longitude"    : "106.848558",
    "locality"     : "Jakarta",
    "countryName"  : "Indonesia"
  },
  "twitter": "nurwijayadi"
};

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.njk into some partials:

  • meta/html.njk,

  • meta/seo.njk,

  • meta/opengraph.njk,

  • meta/twitter.njk.

Skeleton

To avoid complicated code, we are going to use partial for meta tags. This including opengraph and twitter.

  {% include "meta/html.njk" %}
  <title>{{ renderData.title or title or metadata.title }}</title>

  {# CSS, Javascript, and Favicon #}
  ...

  {% include "meta/seo.njk" %}
  {% include "meta/opengraph.njk" %}
  {% include "meta/twitter.njk" %}

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="generator"
        content="Karimata 1.2.3 Media Engine" />  
  <meta name="theme-color"
        content="#2980b9">

  <title>Marilyn Manson - Redeemer</title>
  ...

  <!--w3c-->
  <meta name="author"
        content="marylin">
  <meta name="description"
        content="I am jaded, hiding from the day">
  <meta name="keywords"
        content="industrial metal, 90s, OST, Queen of the Damned">

  <!--opengraph-->
  <meta property="og:image"
        content="https://eleventy-step.netlify.com/assets/images/authors/broken-person.png">
  <meta property="og:locale"
        content="en-us">
  <meta property="og:title"
        content="Marilyn Manson - Redeemer">  
  <meta property="og:type"
        content="article">
  <meta property="og:description"
        content="I am jaded, hiding from the day">  
  <meta property="og:url"
        content="https://eleventy-step.netlify.com/lyrics/marylin-manson-redeemer/">
  <meta property="og:site_name"
        content="Your mission. Good Luck!">
  <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="Marilyn Manson - Redeemer">
  <meta name="twitter:description"
        content="I am jaded, hiding from the day">  
  <meta name="twitter:site"
        content="@nurwijayadi">
...
</head>

You can also optimize twitter card if you want.

11ty SEO: Meta Page Info

You can see them from either page info.

11ty SEO: Meta View Source

Or from view source page.


3: Meta HTML

Consider move important meta to special meta/html.njk. This should be an easy task, since it is only HTML.

Partial: Nunjucks Head

  {% include "meta/html.njk" %}
  <title>{{ renderData.title or title or metadata.title }}</title>

Notice this include "meta/html.njk" code.

Partial: Nunjucks Meta HTML

Remember that, some of these metas must come first in the head tag.

  {# Required meta tags #}
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible"
        content="IE=edge,chrome=1">
  {# Let browser know website is optimized for mobile #}
  <meta name="viewport"
        content="width=device-width, initial-scale=1, shrink-to-fit=no">

You can also add any metadata that you need, such as these lines below:

  {# 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">

  <meta name="HandheldFriendly" content="True">
  <meta name="MobileOptimized" content="320">

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 in frontmatter, and additionally a keywords field, also in frontmatter. For example in a page we can set the frontmatter as below:

tags      : ["industrial metal", "90s"]
keywords  : ["OST", "Queen of the Damned"]

This will result as below:

  <meta name="keywords"
        content="industrial metal, 90s, OST, Queen of the Damned">

Filter: Key Join

To combine tags and keywords, we need other other keyJoin filter in .eleventy.js.

  // Meta Keywords: My own script
  eleventyConfig.addNunjucksFilter("keyJoin", function(tags, keywords) {
    return [...(tags ? tags : []), ...(keywords ? keywords : [])].join(', ');
  });

You can use the filter later as below code:

  {%- set keywordlist = tags  | keyJoin(keywords) -%}

11ty: Filter: Array Merge

Oneliner script is pretty cool for personal use, but if you need to share the script, with your friends across the universe, you need a more verbose script as will be shown as below.

Filter: Key Join: Legacy Code

Considering Code Readability

If that oneliner script above is too cryptic for you, alternatively you can use this old code:

  // Meta Keywords: My own script
  eleventyConfig.addNunjucksFilter("keyJoin", function(tags, keywords) {
    return helper.keyJoin(tags, keywords);
  });

This is my own script.

// Meta Keywords: My own script from Hexo Tutorial
exports.keyJoin = function(tags, keywords) {
  var keys = '';
  var terms = [];

  if (tags) {
    tags.forEach(function(tag){
      terms.push(tag);
    })
  }

  if (keywords) {
    keywords.forEach(function(keyword){
      terms.push(keyword);
    })
  }

  if (terms) {
    keys = terms.join(', ');
  }

  return keys;
}

This [] should also works , with about the same result. I keep this verbose script, to explain what is happened, with each array.

Partial: Nunjucks Meta SEO

Here comes the good parts, using the filter in partials.

  {%- if (author or metadata.author) %}
  <meta name="author"
        content="{{ (author or metadata.author) }}">
  {% endif -%}

  {%- if excerpt %}
  <meta name="description"
        content="{{ excerpt }}">
  {% endif -%}

  {%- set keywordlist = tags  | keyJoin(keywords) -%}
  {%- if keywordlist %}    
  <meta name="keywords"
        content="{{ keywordlist }}">
  {% endif -%}

Page Content: Example

Consider revisit our example below:

---
layout    : post
title     : Marilyn Manson - Redeemer
date      : 2015-07-25 07:35:05

tags      : ["industrial metal", "90s"]
keywords  : ["OST", "Queen of the Damned"]
---

This will result as below:

  <meta name="author"
        content="marylin">
  <meta name="description"
        content="I am jaded, hiding from the day">
  <meta name="keywords"
        content="industrial metal, 90s, OST, Queen of the Damned">

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:image"
        content="https://eleventy-step.netlify.com/assets/images/authors/broken-person.png">
  ...
  <meta property="og:title"
        content="Marilyn Manson - Redeemer">  
  <meta property="og:type"
        content="article">
  <meta property="og:description"
        content="I am jaded, hiding from the day">  
  <meta property="og:url"
        content="https://eleventy-step.netlify.com/lyrics/marylin-manson-redeemer/">
  <meta property="og:site_name"
        content="Your mission. Good Luck!">
  ...
  <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

Nunjucks: 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.

  {%- if opengraphimage -%}
    {%- set ogi = opengraphimage -%}
  {%- elif layout == "post" -%}
    {%- set ogi = metadata.opengraph.postimage -%}
  {%- else -%}
    {%- set ogi = metadata.opengraph.image -%}
  {%- endif -%}

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.

Page Content: Frontmatter Example

layout    : post
title     : Marilyn Manson - Redeemer
...
opengraphimage: "/images/adverts/one-page.png"

This will result as below:

  <meta property="og:image"
        content="https://eleventy-step.netlify.com/images/adverts/one-page.png">

Partial: Nunjucks Meta Opengraph

Most of this partial is just plain representation from metadata.js.

  {%- if opengraphimage -%}
    {%- set ogi = opengraphimage -%}
  {%- elif layout == "post" -%}
    {%- set ogi = metadata.opengraph.postimage -%}
  {%- else -%}
    {%- set ogi = metadata.opengraph.image -%}
  {%- endif -%}

  <meta property="og:image"
        content="{{ metadata.site }}{{ ogi }}">
  <meta property="og:locale"
        content="{{ metadata.opengraph.languageCode }}">
  <meta property="og:title"
        content="{{ renderData.title or title or metadata.title }}">
  {% if layout == "post" %}
  <meta property="og:type"
        content="article">
  {% endif %}
  {%- if excerpt %}
  <meta property="og:description"
        content="{{ excerpt }}">
  {% endif %}
  <meta property="og:url"
        content="{{ metadata.site }}{{ page.url }}">
  <meta property="og:site_name"
        content="{{ metadata.title }}">

  <meta property="og:latitude"
        content="{{ metadata.opengraph.latitude }}"/>
  <meta property="og:longitude"
        content="{{ metadata.opengraph.longitude }}"/>
  <meta property="og:locality"
        content="{{ metadata.opengraph.locality }}"/>
  <meta property="og:country-name"
        content="{{ metadata.opengraph.countryName }}"/>

6: SEO: Twitter

The twitter version is simpler. Because, I do not really pay attention to twitter.

Partial: Nunjucks Meta Twitter

Very short and simple

  <meta name="twitter:title"
        content="{{ renderData.title or title or metadata.title }}">

  {%- if excerpt %}
  <meta name="twitter:description"
        content="{{ excerpt }}">
  {% endif -%}

  {%- if metadata.twitter %}
  <meta name="twitter:site"
        content="@{{ metadata.twitter }}">
  {% endif -%}

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

11ty Meta: Opengraph Telegram Smartphone

Slack on Smartphone

11ty Meta: Opengraph Slack 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 [ Eleventy - Content - Blog Post ]. We are going to move on to blog post content. It is the header, footer, and navigation.

Thank you for reading.