How I improved my Jekyll SEO


Shortly after I went live with my blog on Jekyll, I noticed that not all of my content was presented correctly to search engines, social media sites and my readers using RSS feeds. Here is what I’ve found out and how I fixed it.

Articles in this series:

  1. Migrated from Ghost to Jekyll
  2. Migrating content from Ghost to Jekyll
  3. How I set up this blog on Jekyll
  4. How I improved my Jekyll setup
  5. How I improved my Jekyll SEO (this article)
  6. Fixing my content after migrating to Jekyll

Improve SEO and publishing

I use my blog to share my experience. Whether it’s related to working with SharePoint, Office 365 or Azure, presenting at events or working in general. While it’s gratifying to see that I have a steady group of readers, I’m not optimizing my articles for keywords. I simplify my URLs, use headings and try to use relevant titles, but that’s about it when it comes to SEO of my content. On the technical side, I strive to use semantic markup and provide some metadata to make it easier to find and digest my content.

Every CMS has its way of doing things. Having worked with a couple of them, what I like about static site generators is, that they don’t abstract anything away. You have full control of the generated HTML and you can decide yourself how much metadata you want to provide for each piece of content to be published along. You can even make up your own metadata without having to worry about how you can adjust the UI to accommodate managing it.

The theme

I started with a theme that I found visually attractive and that would be sufficient for all my needs. I verified that it used semantic markup and I could focus on adding a few extra details and not having to rewrite the whole markup.

URL structure

For a long time, I’ve been using simplified URLs on my blog, where the URL would consist only of the page’s slug: lowercase title cleared of all noise words, such as in, the, at, etc. It shortens the URL and increases the relevance of keywords matching the search query.

To implement this pattern in Jekyll, I had to do two things:

  1. In each post, I define the slug which should be used in the URL (not the permalink!)
  2. In the site’s config, I set up the URL as follows:
permalink: /:slug/

This allows me to centrally manage the URL of all my articles including having a trailing slash which I had in the past as well.

Enable the jekyll-seo-tag plugin

Next, I enabled the jekyll-seo-tag plugin. It’s the defacto standard for extending Jekyll sites with SEO information. The plugin is pretty complete and covers many things: from page title, canonical URL and Open Graph meta tags to twitter card and metadata in JSON format. Using the plugin is very easy. All you need to do is to add: {% raw %}{% seo %}{% endraw %} to the <head> section of your layout and you’re done. The more metadata about your site you specify in your config, the more complete the output will be. For my site, I specified the following properties:

author:
  name: Waldek Mastykarz
  twitter: waldekm
  image: waldek.jpg
twitter:
  username: waldekm
  card: summary_large_image
social:
  name: Waldek Mastykarz
  links:
    - https://twitter.com/waldekm
    - https://www.facebook.com/waldek
    - https://www.linkedin.com/in/waldekmastykarz
    - https://github.com/waldekmastykarz

All other properties, like title, description or preview image, are pulled in from the pages themselves.

Configure my own title

One thing I didn’t like about the jekyll-seo-tag plugin, was how it generated the page title. It uses a predefined format where the different elements are separated by a | (pipe). For one, the pipe is a character that sounds awkwardly when read by a screen reader, but I wanted to use a different format anyway.

Replacing the title generated by the jekyll-seo-tag plugin is easy. First, you need to instruct the plugin that you don’t want it to generate a <title> element. You do that by changing:

{% raw %}{% seo %}```

to:

```html
{% raw %}{% seo title=false %}```

Next, I defined my own title. I wanted to have the following:

- on the home page, the title should be: `site name - site description`, eg. `Waldek Mastykarz - Innovation Matters`
- when you go to the second and following page of the archive, the title should be `Page x of y - site name - site description`, eg. `Page 2 of 10 - Waldek Mastykarz - Innovation Matters`
- when you go to a blog post or a page, the title should be `page title - site name`, eg. `How I improved my Jekyll SEO and publishing - Waldek Mastykarz`
- when you go to the first tag page, the title should be `tag name - site name`, eg. `office-365 - Waldek Mastykarz`
- when you go to the second and following page of a tag archive, the title should be `Page x of y - tag name - site name`, eg. `Page 2 of 10 - office-365 - Waldek Mastykarz`

I implemented it using the following liquid template:

```html
{% raw %}<title>{% if page.tag or page.title %}{% if page.tag %}{{ page.tag | escape }}{% else %}{{ page.title | escape }}{% endif %} - {% endif %}{% if paginator and paginator.page and paginator.total_pages > 1 and paginator.page > 1 %}Page {{ paginator.page }} of {{ paginator.total_pages }} - {% endif %}{{ site.name | escape }}{% unless page.tag or page.title %} - {{ site.description | escape }}{% endunless %}{% endraw %}</title>

It seems complex at first, but it’s easier to grasp when you break it to multiple lines and indent the related blocks:

{% raw %}<title>
  {% if page.tag or page.title %}
    {% if page.tag %}
      {{ page.tag | escape }}
    {% else %}
      {{ page.title | escape }}
    {% endif %}
    -
  {% endif %}
  {% if paginator and paginator.page and paginator.total_pages > 1 and paginator.page > 1 %}
    Page {{ paginator.page }} of {{ paginator.total_pages }} -
  {% endif %}
  {{ site.name | escape }}
  {% unless page.tag or page.title %}
    - {{ site.description | escape }}
  {% endunless %}
</title>```

Initially, I showed the site description on all pages, but later I changed it to be showed only on the home page. I did it to shorten page titles and increase the relevance of keywords matching search results. Plus, the description would often get trimmed anyway, so there was little point in including it.

### Show post image in RSS feeds

I use RSS **a lot**. That's how I stay up-to-date with what's happening in the tech world. Occasionally, I'll see something shared on social, but following several high-quality RSS feeds gives me a glance into the most relevant things for me. I use [Feedly](https://feedly.com) to manage and read my RSS feeds and I can recommend it for three reasons:

1. I can access all feeds across all my devices
1. It's very convenient to use, including automatically marking as read articles you've scrolled past.
1. It's free

After publishing my blog on Jekyll I noticed, that some of my articles wouldn't have a preview image when viewed in Feedly. I've been including preview images for all posts for a while now, and yet, for some reason, Feedly would show some articles without the image.

<picture>
  <source srcset="/assets/images/2019/10/jekyll-feedly-no-images.webp" type="image/webp">
  <img src="/assets/images/2019/10/jekyll-feedly-no-images.png" alt="Feedly showing a list of articles, two of which have no preview image">
</picture>

Eventually I found out [how Feedly decides if and which image to show](https://blog.feedly.com/10-ways-to-optimize-your-feed-for-feedly/). To ensure, that all my articles would show the preview image that I have defined, I extended my RSS template to:

```xml
{% raw %}<description>{% if post.image %}&lt;p&gt;&lt;img src="{{ post.image | prepend: site.baseurl | prepend: site.url }}" alt="{{ post.title }}" class="webfeedsFeaturedVisual"&gt;&lt;/p&gt;{% endif %}{{ post.content | xml_escape }}</description>```

If the specific article has a preview image defined in the `image` property, it should be rendered in the `<img>` tag with the `webfeedsFeaturedVisual` CSS class. Rather than having Feedly try to guess which image to show for the given article, the above tag instructs it to use this particular image in the preview.

<picture>
  <source srcset="/assets/images/2019/10/jekyll-feedly-images.webp" type="image/webp">
  <img src="/assets/images/2019/10/jekyll-feedly-images.png" alt="Feedly showing a list of articles with preview images">
</picture>

### Define large twitter card

Some of my content gets shared on twitter. Shortly after publishing my blog I noticed, that it would be shown using the small twitter card. Large cards are more clearly visible in the stream plus give a better overview of the content and the preview image, both of which convey the message. I've been using large cards in the past when my blog was on Ghost and I wanted to keep using them in Jekyll as well.

Eventually, I found out, that all I needed to do, is to change the configuration of the jekyll-seo-tag plugin in the site's config file from:

```yaml
twitter:
  card: summary

to:

twitter:
  card: summary_large_image

All the necessary information was already there.

Large twitter card of an article of this blog shared on twitter

Build AMP pages

Opinions are divided about to what extent AMP pages influence SEO. Since I’ve been using them in the past, and didn’t want to take any chances, I wanted to offer them on my blog hosted on Jekyll as well.

I started with using the amp-jekyll plugin. The plugin supports by default only images, but I extended it to handle scripts and iframes as well. To speed up generation of AMP pages, I combined the filters to process the contents of each page only once. To avoid breaking AMP URLs from Ghost, I updated the path to where the AMP plugin would put the generated pages.

Disable page fade-in effect

My Jekyll theme originally came with a fancy JavaScript effect, which would make the body of the page fade-in after the content has loaded. While it looked neat, when using Google PageSpeed Insights, I noticed that it led to lower scores due to a longer time to meaningful paint. Until the content was loaded, the body area was remaining empty. After removing the effect, the content started to show almost instantaneously, directly improving the page score.

Configure timezone

After building my site, I noticed, that some articles would show different publishing dates, than what I had defined in the front matter. I define my dates as 2019-09-17 18:51:12 without any time zone indicator and for a moment I was afraid, that I’d have to build a script to update all dates in my posts. Fortunately, Jekyll offers the timezone option, which you can use to specify the default timezone for your dates.

Updating my configuration with:

timezone: Europe/Amsterdam

fixed my publishing dates without having to update the articles.

I have quite some content available on my blog and I’d like to help my visitors to find whatever they’re looking for as easily as possible. That means, that I don’t want them to leave my blog and I don’t want to blindly rely on internet search engines. Similarly to Ghost, Jekyll doesn’t offer native support for search. In a way, it’s not surprising, given that Jekyll is a static site generator and not a CMS. Luckily, I had an Azure Search setup in place that I used with Ghost and which I also use with Jekyll. Since it’s a bigger topic, I will tell you more about the setup in a future article.

What’s next

I hope you found it helpful and got a couple of ideas to try out on your Jekyll site. In the next articles, I will tell you how I optimized my writing process and how I solved a few issues with my content after migrating it from Ghost. Stay tuned!

Others found also helpful: