Good blog posts have images that can be used as thumbnails for the posts inside and outside of the blog itself.

I’m sure Octopress has this covered but with Jekyll there’s some need for custom plugins. When I was looking around, there were lots of full asset pipelines for Jekyll but I don’t think I need something that complicated.

The simple approach was that each post have in their YAML front-matter a new extra custom tag image::

---
title: Smartly cropped thumbnails for blog posts with Jekyll
image: /images/smartcrop.png
---

Good blog posts have images that can be used as thumbnails for the posts inside and outside of the blog itself.

Note that in my approach the URL for image is the final relative published one for the full image. If this is not the case for your blog, you will need somewhat more complex code to handle that.

Another approach might be to parse the content of the post and pick the first image in the post, but this fails for postings that do not have real images in the body. A more thorough solution would look for both explicit and implied blog images.

The way I have done this on my Jekyll blog you are reading right now is that I have a custom generator that goes through all the posts that have the image tag set, resizes them to the appropriate size (in my case 96x96), and adds these as PostlThumbnailImages to Jekyll’s site.static_files.

I also have PostImages generator that just makes sure that I’m not serving overly sized files and resizes all images in _/image/ folder that are larger than the website needs. This is why the images are in a folder prefixed with an underscore that Jekyll doesn’t automatically touch when generating the site.

# post_thumbnail_generator.rb
require "smartcropper"

module Jekyll

  class PostThumbnailImage < StaticFile
    def initialize(site, base, dir, name)
      @site = site
      @base = base
      @dir = dir
      @dest_dir = File.join("images", "thumbnail")
      @name = name
    end

    def destination(dest)
      File.join(dest, @dest_dir, @name)
    end

    def write(dest)
      dest_path = destination(dest)

      return false if File.exist?(dest_path) and !modified?
      @@mtimes[path] = mtime

      FileUtils.mkdir_p(File.dirname(dest_path))
      SmartCropper.from_file(path).smart_crop_and_scale(96, 96).write(dest_path)

      true
    end
  end

  class PostThumbnailGenerator < Generator
    def generate(site)
      site.posts.each {|post|
        if post.data.has_key?("image")
          site.static_files << PostThumbnailImage.new(site, site.source, "_images", File.basename(post.data["image"])) # Only works if post image is in src/_images/ folder.
        end
      }
    end
  end

end

Resizing is easy, but many services and my website prefer square thumbnails so I also need to crop the post images to a 1:1 aspect ratio. Instead of just centering on the image, there are smart ways to do the cropping by checking where the action in the image is and focus on that (ie. content aware cropping). I’ve used the smartcropper gem to do this magic and it works pretty well.

To finish things off, I’ve also created a Liquid filter that takes the full image url and replaces that with the thumbnail’s. As the only difference is in the path (in my case), it’s quite straightforward:

module Jekyll
  module PostThumbnail
    def post_thumbnail(input)
      input.sub("images/", "images/thumbnail/")
    end
  end
end

Liquid::Template.register_filter(Jekyll::PostThumbnail)

This can be then used in a Jekyll template like this:


<img src="{{ post.image || post_thumbnail }}" alt="Thumbnail image for the post '{{ post.title }}'"></a>

The main use for post.image (and it’s thumbnail version) is in your own blog’s design but it can also be used for the metadata for the posts.