DEV Community

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

Discussion (5)

pic
Editor guide
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 Author

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 Author

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