diff --git a/_posts/2024-08-07-opengraph-image-generator.md b/_posts/2024-08-07-opengraph-image-generator.md new file mode 100644 index 0000000..86465ef --- /dev/null +++ b/_posts/2024-08-07-opengraph-image-generator.md @@ -0,0 +1,227 @@ +--- +layout: post +title: "Generate and display OpenGraph images" +author: Yaroslav Shmarov +tags: ruby rails ruby-on-rails SEO meta-tags ferrum +thumbnail: /assets/thumbnails/url.png +--- + +[This post](https://www.reddit.com/r/rails/comments/1eiyect/what_do_you_use_for_generating_opengraph_images/) inspired me to write this article: + +![og-auto-q-reddit](/assets/images/og-auto-q-reddit.png) + +Previously I wrote about setting [Meta tags in a Rails app]({% post_url 2021-10-28-meta-tags-without-a-gem %}). Meta tags really make your web pages more "shareable". + +But generating OpenGraph images can be a challenge. There are many businesses built around this. For example: + +- [Tinyzap](https://tinyzap.com) +- [htmlcsstoimage](https://htmlcsstoimage.com/demo) +- [Vercel og image generator](https://vercel.com/docs/functions/og-image-generation) +- [Thumbsmith](https://thumbsmith.com/) +- [Jekyll OG generator](https://x.com/igor_alexandrov/status/1754479670953676963) +- [ogimage.org](https://x.com/illyism/status/1763843779239329842) + +But you are a great developer! You don't need to pay for a tool that you can just build, right? + +Let's build an OpenGraph image generator! + +We could use [Rmagick]({% post_url 2022-10-03-rmagick-activestorage %}) to draw images with their API. + +But maybe an easier approach would be to generate some HTML, open it in a browser, and take a screenshot. + +For this we will use [Gem Ferrum]({% post_url 2024-01-27-gem-ferrum-generate-pdf %}) that is a headless Chrome API. + +There are a few levels of coolness/complexity for taking screenshots of an url: + +- Level 1.1. Take a screenshot of an URL +- ~~Level 1.2. Take a screenshot of an URL selector (id/class)~~ +- Level 2.1. Take a screenshot of an URL with a **dedicated** template +- ~~Level 2.2. Visit web page, parse the meta `%i[title, description, logo, date]` & autocomplete your **generic** template~~ + +### Level 1.1. Take a screenshot of an URL + +Example of final result - a screenshot of a page: + +![og-screenshot](/assets/images/https-superails-com-posts-rails-160-meta-tags-open-graph-seo-social-sharing-previews-playlist-build-an-opengraph-automation-tool.png) + +We will leverage the [Ferrum Screenshot API](https://github.com/rubycdp/ferrum#screenshots): + +```ruby +# rails g job UrlToImage +# url = "https://superails.com/posts/181-search-and-autocomplete-french-company-information" +# UrlToImageJob.perform_now(url) +class UrlToImageJob < ApplicationJob + queue_as :default + + def perform(url) + browser = Ferrum::Browser.new + browser.resize(width: 1200, height: 630) + browser.goto(url) + # browser.screenshot(path: "tmp/screenshots/#{url.parameterize}.jpg") + # browser.screenshot(path: "tmp/screenshots/#{url.parameterize}.jpg", quality: 40, format: "jpg") + # browser.screenshot(path: "tmp/screenshots/#{url.parameterize}.jpg", quality: 40, format: "jpg", full: true) + # sleep 0.5 + # browser.screenshot(path: "app/assets/images/opengraph/#{url.parameterize}.jpg", quality: 40, format: "jpg", selector: "main") + browser.screenshot(path: "app/assets/images/opengraph/#{url.parameterize}.jpg", quality: 40, format: 'jpg') + ensure + browser.quit + end +end +``` + +ℹ️ Generating JPEG can be faster than PNG. + +And here's a helper to access the generated image based on the current URL: + +```ruby +# app/helpers/application_helper.rb + def meta_opengraph_image_asset_path + base_url = Rails.application.config_for(:settings).dig(:site, :url, :production) + image_name = [base_url, request.path].join.parameterize + full_path = "opengraph/#{image_name}.png" + helpers.image_url(full_path) + rescue StandardError + image_url('logo.png') + end +``` + +Display the image in meta tags + +```ruby +# posts/show.html.erb +<%= content_for :head do %> + <%= tag.meta(property: 'og:image', content: meta_opengraph_image_asset_path) %> + <%= tag.meta(property: 'twitter:card', content: "summary_large_image") %> +<% end %> +``` + +This way we store the image in our assets. It is for you to decide a better way to **deliver** these images to production. + +### Level 2.1. Take a screenshot of an URL with a **dedicated template** + +Example of final result: + +![og-ferrum-with-layout](/assets/images/og-ferrum-with-layout.png) + +Instead of visiting an URL, we can just render plain HTML in Ferrum and take a screenshot of it: + +```ruby +# https://github.com/rubycdp/ferrum/blob/main/lib/ferrum/frame.rb#L109 +browser = Ferrum::Browser.new +browser.resize(width: 1200, height: 630) +frame = browser.frames.first +frame.body +# => "
" +frame.content = "Voila!" +frame.body +# => "Voila!" +``` + +Now we can create a layout and template for our open graph images! + +A minimalistic CSS-only layout: + +```html + + + + +