loading...

Creating images with Ruby + HTML/CSS API

mscccc profile image Mike Coutermarsh Updated on ・2 min read

Ever wonder how trendy websites like Product Hunt or Medium generate those fancy Twitter screenshots?

Wonder no more!

Essentially, they render the HTML/CSS in a headless Chrome instance and take a screenshot. Sound complicated? Yes, it is. Especially "at scale."

Good thing there are API's for this.

I will show you how to do this with an API I built specifically for this purpose: HTML/CSS to Image API.

require "httparty"


auth = { username: 'user_id', password: 'api_key' }

html = "<div class=\"box\"><h3>Hello, world 😍</h3></div>"

css = ".box {
  border: 4px solid #8FB3E7;
  padding: 20px;
  color: white;
  font-size: 100px;
  width: 800px;
  height: 400px;
  font-family: 'Roboto';
  background-color: #8BC6EC;
  background-image: linear-gradient(135deg, #8BC6EC 0%, #9599E2 100%);
}"

image = HTTParty.post("https://hcti.io/v1/image",
                      body: { html: html, css: css },
                      basic_auth: auth)

# { url: https://hcti.io/v1/image/bfae7d68-86cc-4934-83ac-af3ba75a0d34 }

Hello, world.

Done. We have an image ✨.

Rails + Caching

If you're creating these from Rails and already use memcached, here's a nice trick for ensuring you only create them once.

cache_key = "image/#{html}/#{css}"

image = Rails.cache.fetch(cache_key) do
  HTTParty.post("https://hcti.io/v1/image",
                      body: { html: html, css: css },
                      basic_auth: auth)
end

We use the html/css to generate the cache key. Don't worry about it being too long. Rails/Dalli automatically hashes the key for you, guaranteeing it will be unique and the correct length.

This way, if you send the exact same payload again, Rails will pull the URL from cache rather than regenerating it.

Another Rails tip

If you're generating these for all of your content, it may be temping to stick the API call into an after_create. I advise against that. It's always best to keep I/O to an absolute minimum when "in request". Even though the request may only take 30ms, that can add up if the endpoint is already doing a lot.

The solution, is using a background job.

So instead of making the call directly in an after_create. You can instead enqueue the background job using after_create. Then the image will be generated in the background. Keeping your Rails response times super quick.

Build something cool?

If you build something cool this, let me know so I can tweet it!!

Posted on by:

Discussion

markdown guide
 

Hey Mike, I’ve been following this and I think we’ll be users/customers for DEV but rewriting our current solution hasn’t been a priority.

Feel free to nudge me on this front 😋

What we’re currently doing:

github.com/thepracticaldev/dev.to/...

 

Yeah!! I know a Ruby dev who could submit a PR to help with the upgrade. ✨

 

I didn't come out and say it but your motivations and our laziness aligns pretty well here.

If you want to re-implement our social images, I'll sign up and get us a key. 😄

Expect to see a PR sometime this week. 😀

 

I have created such gem github.com/igorkasyanchuk/omg_image to create such images without 3rd party services