Social media preview images for static website

In this article, we’ll generate social media preview images for a static website in Ruby. In the end, we’ll be able to expose them in og:image and twitter:image meta tags.

We’ll add them to a blog generated by nanoc framework. But the idea translates to other static site generators like Jekyll or even dynamic frameworks like Rails.

Here’s what we’ll have to do:

  1. generate an image,
  2. add it to nanoc compilation and
  3. update the layout.

Generate image

We’ll take a background image and dynamically add a text on top of it. For that, we will use rmagick gem. And then the Magick::Draw#annotate method.

The dimensions of the background image should be 1200x630px and under 8MB according to the spec.

Lastly, we want to make sure that the text fits and if it’s longer it wraps properly.

require "rmagick"

class OpenGraphImage
  def initialize(title, output_path)
    @title = title
    @output_path = output_path
  end

  def generate
    image = Magick::Image.read("background.jpg").first

    create = Magick::Draw.new

    create.annotate(image, 0, 0, 3, 0, word_wrap(@title)) do |txt|
      txt.font = 'SF-Pro-Display-Bold.otf'
      txt.pointsize = 65
      txt.font_weight = Magick::BoldWeight
      txt.fill = 'white'
      txt.gravity = Magick::CenterGravity
    end

    image.write("jpg:" + @output_path)
  end

  private

  def word_wrap(line)
    return line if line.length <= 26
    line.gsub(/(.{1,26})(\s+|$)/, "\\1\n").strip
  end
end

Add to nanoc compilation

Nanoc uses filters to manipulate files, so let’s create one that uses our OpenGraphImage class. We are converting markdown files to images so the type of the filter is text: :binary.

For the text, we’re using the article’s title.

class GenerateOpenGraphImage < Nanoc::Filter
  identifier :generate_open_graph_image
  type text: :binary

  def run(content, params = {})
    OpenGraphImage.new(item[:title], output_filename).generate
  end
end

Then in our Rules file, we’ll add another compilation rule. We can’t have two rules processing the same file unless we add a rep to it. Since we’re generating jpegs let’s use rep:jpg.

compile "/articles/**/*.md", rep: :jpg do
  filter :generate_open_graph_image

  write "/og/#{item.identifier.without_ext}.jpg"
end

Update layout

And the last thing missing is adding the new image paths to the head meta tags of the default layout.

<meta property="twitter:image" content="https://example.com<%= "/og/#{item.identifier.without_ext}.jpg" %>" />
<meta property="og:image" content="https://example.com<%= "/og/#{item.identifier.without_ext}.jpg" %>">

And we’re done. For each article, we are automatically generating the social media preview images.

A useful tool for debugging these previews are Facebook’s Sharing Debugger and Open Graph Preview.


Would you like to get the most interesting content about programming every Monday?
Sign up to Programming Digest and stay up to date!