How dev.to dynamically generates social images
Tags: meta, ruby, webdev
Published: 2018-02-26 21:49:38

---
title: How dev.to dynamically generates social images
published: true
description: We get a lot of questions about how we generate these images dynamically. Here is the answer.
tags: meta, ruby, webdev
---

It's pretty important that [dev.to](/) posts with no cover image have a well-designed "card" that grabs a Twitter user's attention as they scroll through the feed. I'm not sure ours are all that well-designed, but they _could be_, and that part isn't really about the code.

We get a lot of questions about how we generate these default images dynamically. I've hesitated to write this post because I still think we could do this a better way. But since we have not gotten around to doing it differently, here is a post about our current approach and a description of possible alternative solutions.

### Here is an example Twitter card for a #discuss post

![](https://thepracticaldev.s3.amazonaws.com/i/5wug0xrfibr56uhe3o1w.png)

## What we do

We use [Cloudinary](https://cloudinary.com) as an image management/manipulation service and as a CDN. They have features such as [adding text overlays to images](https://cloudinary.com/blog/adding_watermarks_credits_badges_and_text_overlays_to_images) which we use to generate images. So it's ultimately a matter of fiddling with the layers until you get it right and then writing the code so our app interacts with the service programatically. It's pretty straightforward, but our creativity is a bit limited by the narrow range of things we can do within the system. It's a great service but we generate image templates that are kind of a pain to change. Code that is hard to change is not good code.

## Other approaches we could take

### Generate the image with HTML and then using a service to take a screengrab

This would give us more flexibility and control over the design. I think this would be our ideal approach. Cloudinary itself provides this service as a plugin but we have not gotten around to moving to it.

### Dynamically generating an image on our server.

[ImageMagick](https://www.imagemagick.org/script/index.php) is a classic library with lots of extensibility. It's very popular but kind of a pain to work with in my opinion. Services like Cloudinary exist because ImageMagick is a pain. I'm sure there are other libraries depending on your backend ecosystem, but I'm not aware of what the good choices are.

### Services like [ShareMeow](https://github.com/producthunt/ShareMeow)

For a very specific type of generated image, ShareMeow is an interesting self-hosted web service we have played around with.

## Takeaways

What we are doing works, but I'm not sure I'd recommend it. If I were you I'd skip straight to generating these images via HTML or some other nice service.

# The other interesting part of the whole process

There is more to the process than simply generating the image. We also need a cache-busting bot in order to update the card periodically. If we want to show a growing number of comments over time, we cannot wait for Twitter to bust the cache once per week. It still won't update instantly, but if a tweet has been floating around for a while it will show an accurate comment count even if it is more than when the tweet went out originally.

Twitter does not expose their cache-busting feature via API, so we had to ping it via browser automation.

It's a Ruby file which makes use of Capybara and Selenium to automate the process of pinging the Twitter card cache validation. It makes use of basically these three methods:

```ruby
  def visit_twitter_and_log_in
    visit 'https://cards-dev.twitter.com/validator'
    find('input.js-username-field').set(ENV['TWITTER_USERNAME'])
    find('input.js-password-field').set(ENV['TWITTER_PASSWORD'])
    click_on('Log in')
  end
```

```ruby
  def enter_url_and_click_preview(url)
    find('input.FormControl').set(url)
    click_on('Preview card')
    result = has_content?('Page fetched successfully')
    visit 'https://cards-dev.twitter.com/validator'
  end
end
```

That second method needs to be pinged a few times with some junk cache-busting. This is the method that calls it:

```ruby
  def enter_urls
    urls.each do |url|
      3.times do
        enter_url_and_click_preview("#{url}?#{rand(10_000)}=#{rand(10_000)}")
      end
      enter_url_and_click_preview(url)
    end
  end
```

That's it. Building all of this was a nice way to automate some potentially manual work. It all goes towards making the [dev.to](/) service more valuable for more users when they share content.