OG-зображення в Ruby on Rails
Створюйте OG-зображення в Ruby on Rails за допомогою підписної Dynamic API від FastOG — для цього потрібна підписка на Dynamic API (створіть свій API-ключ у панелі керування). Перегляньте результат у безкоштовному тестері OG-зображень, а потім генеруйте динамічні Open Graph-зображення з вашого Rails-застосунку за допомогою URL-адрес із HMAC-підписом.
Швидкий старт
1. Створення сервісу підпису
# app/services/fastog_signer.rb
class QuickogSigner
def self.generate_signed_og_url(params, options = {})
base_url = options[:base_url] || ENV.fetch("FASTOG_BASE_URL", "https://fastog.com/api/v1/og")
api_key = options[:api_key] || ENV.fetch("FASTOG_API_KEY")
hmac_secret = options[:hmac_secret] || ENV.fetch("FASTOG_HMAC_SECRET")
raise "Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET" unless api_key && hmac_secret
all = params.merge(key: api_key)
pairs = all.flat_map do |k, v|
Array(v).sort.map { |vv| "#{enc(k)}=#{enc(vv)}" }
end
canonical = pairs.sort.join("&")
signature = OpenSSL::HMAC.hexdigest("SHA256", hmac_secret, "GET/api/v1/og\n#{canonical}")
"#{base_url}?#{URI.encode_www_form(all.merge(s: signature))}"
end
def self.enc(s)
s.to_s.gsub(/[^A-Za-z0-9\-._~]/) { |c| c.bytes.map { |b| "%%%02X" % b }.join }
end
private_class_method :enc
end
Чому власний enc? Він виконує процентне кодування з великими літерами у шістнадцятковому форматі, залишаючи лише -._~ незарезервованими — тому пробіли перетворюються на %20, а не на +, що відповідає rawurlencode сервера. Якщо ви використовуєте CGI.escape або URI.encode_www_form_component, обидва генерують + для пробілів: спершу нормалізуйте за допомогою .gsub('+', '%20').
2. Конфігурація
# config/application.rb
config.fastog = ActiveSupport::OrderedOptions.new
config.fastog.api_key = ENV.fetch("FASTOG_API_KEY", nil)
config.fastog.hmac_secret = ENV.fetch("FASTOG_HMAC_SECRET", nil)
config.fastog.base_url = ENV.fetch("FASTOG_BASE_URL", "https://fastog.com/api/v1/og")
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
Контролер
# app/controllers/blog_controller.rb
class BlogController < ApplicationController
def show
@post = Post.find_by!(slug: params[:slug])
@og_image_url = QuickogSigner.generate_signed_og_url(
template: "blog",
title: @post.title,
subtitle: @post.excerpt,
author: @post.author.name
)
end
end
Перегляд
<!-- app/views/blog/show.html.erb -->
<% content_for :head do %>
<meta property="og:image" content="<%= @og_image_url %>" />
<meta property="og:title" content="<%= @post.title %>" />
<meta property="og:description" content="<%= @post.excerpt %>" />
<% end %>
<article>
<h1><%= @post.title %></h1>
<p><%= @post.excerpt %></p>
</article>
Макет
<!-- app/views/layouts/application.html.erb -->
<head>
<%= yield :head %>
</head>
Допоміжний засіб
# app/helpers/fastog_helper.rb
module QuickogHelper
def og_image_url(template:, **params)
QuickogSigner.generate_signed_og_url({ template: template }.merge(params))
end
end
<!-- In view -->
<meta property="og:image" content="<%= og_image_url(template: 'blog', title: @post.title) %>" />
Приклад для електронної комерції
# app/controllers/products_controller.rb
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
@og_image_url = QuickogSigner.generate_signed_og_url(
template: "ecommerce",
title: @product.name,
price: @product.formatted_price,
image: @product.image_url
)
end
end
Приклад допису в блозі
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find_by!(username: params[:username])
@og_image_url = QuickogSigner.generate_signed_og_url(
template: "blog",
title: @user.name,
subtitle: @user.bio,
author: @user.name
)
end
end
Кінцева точка API
# config/routes.rb
get "api/og", to: "api/og#generate"
# app/controllers/api/og_controller.rb
module Api
class OgController < ApplicationController
skip_before_action :verify_authenticity_token
def generate
title = params[:title]
template = params[:template] || "default"
unless title
return render json: { error: "Missing title" }, status: :bad_request
end
og_image_url = QuickogSigner.generate_signed_og_url(
template: template,
title: title
)
render json: { ogImageUrl: og_image_url }
end
end
end
Turbo/Hotwire
<!-- app/views/blog/_post.html.erb -->
<%= turbo_frame_tag "post_#{@post.id}" do %>
<meta property="og:image" content="<%= og_image_url(template: 'blog', title: @post.title) %>" />
<h2><%= link_to @post.title, blog_path(@post) %></h2>
<% end %>
Типові помилки
Підпис неправильного повідомлення
❌ Неправильно: "GET/api/v1/og#{canonical}" — пропущено розділювач нового рядка.
✅ Правильно: "GET/api/v1/og\n#{canonical}" — метод + шлях, новий рядок, а потім канонічний запит.
Залишення key поза підписом
❌ Неправильно: підписуєте лише свої параметри вмісту та додаєте key до URL після цього.
✅ Правильно: key об'єднується з параметрами до побудови канонічного запиту та з'являється в кінцевому URL.
401 missing_signature / invalid_signature
missing_signature (401) означає, що key або s відсутній у URL. invalid_signature (401) означає, що канонічний запит не збігся — перевірте наявність нового рядка в підписаному повідомленні, однаковий hmac_secret з обох боків, а також те, що enc кодує пробіли як %20 (ніколи як +).
Розкриття HMAC-секрету
Ніколи не розкривайте FASTOG_HMAC_SECRET у фронтенд-JavaScript або клієнтському коді.