DEV Community

Mike Coutermarsh
Mike Coutermarsh

Posted on • Updated on

Creating images with Ruby + HTML/CSS API

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!!

Oldest comments (5)

Collapse
 
ben profile image
Ben Halpern

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/...

Collapse
 
mscccc profile image
Mike Coutermarsh

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

Collapse
 
ben profile image
Ben Halpern

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. 😄

Thread Thread
 
mscccc profile image
Mike Coutermarsh

Expect to see a PR sometime this week. 😀

Collapse
 
igorkasyanchuk profile image
Igor Kasyanchuk

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