# config/routes.rb
resources :posts do member do get :card_preview # e.g., /posts/1/card_preview end
end
# config/routes.rb
resources :posts do member do get :card_preview # e.g., /posts/1/card_preview end
end
# config/routes.rb
resources :posts do member do get :card_preview # e.g., /posts/1/card_preview end
end
<!-- app/views/layouts/card.html.erb -->
<!DOCTYPE html>
<html> <head> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="w-[1200px] h-[630px] m-0 p-0 flex items-center justify-center bg-slate-900 text-white"> <%= yield %> </body>
</html>
<!-- app/views/layouts/card.html.erb -->
<!DOCTYPE html>
<html> <head> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="w-[1200px] h-[630px] m-0 p-0 flex items-center justify-center bg-slate-900 text-white"> <%= yield %> </body>
</html>
<!-- app/views/layouts/card.html.erb -->
<!DOCTYPE html>
<html> <head> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="w-[1200px] h-[630px] m-0 p-0 flex items-center justify-center bg-slate-900 text-white"> <%= yield %> </body>
</html>
<!-- app/views/posts/card_preview.html.erb -->
<div class="p-16 w-full h-full flex flex-col justify-between"> <h1 class="text-8xl font-bold"><%= @post.title %></h1> <div class="flex items-center text-4xl text-gray-400"> <span>By <%= @post.author_name %></span> </div>
</div>
<!-- app/views/posts/card_preview.html.erb -->
<div class="p-16 w-full h-full flex flex-col justify-between"> <h1 class="text-8xl font-bold"><%= @post.title %></h1> <div class="flex items-center text-4xl text-gray-400"> <span>By <%= @post.author_name %></span> </div>
</div>
<!-- app/views/posts/card_preview.html.erb -->
<div class="p-16 w-full h-full flex flex-col justify-between"> <h1 class="text-8xl font-bold"><%= @post.title %></h1> <div class="flex items-center text-4xl text-gray-400"> <span>By <%= @post.author_name %></span> </div>
</div>
gem 'ferrum'
gem 'ferrum'
gem 'ferrum'
# app/services/card_generator.rb
require 'ferrum' class CardGenerator def self.capture(url) # Boot the headless browser browser = Ferrum::Browser.new( window_size:[1200, 630], timeout: 10 ) # Go to our special Rails view browser.goto(url) # Optional: Wait for any custom fonts or images to load browser.network.wait_for_idle # Take the screenshot and save it to a temporary file temp_file = Tempfile.new(['card', '.png']) browser.screenshot(path: temp_file.path, format: :png) browser.quit temp_file end
end
# app/services/card_generator.rb
require 'ferrum' class CardGenerator def self.capture(url) # Boot the headless browser browser = Ferrum::Browser.new( window_size:[1200, 630], timeout: 10 ) # Go to our special Rails view browser.goto(url) # Optional: Wait for any custom fonts or images to load browser.network.wait_for_idle # Take the screenshot and save it to a temporary file temp_file = Tempfile.new(['card', '.png']) browser.screenshot(path: temp_file.path, format: :png) browser.quit temp_file end
end
# app/services/card_generator.rb
require 'ferrum' class CardGenerator def self.capture(url) # Boot the headless browser browser = Ferrum::Browser.new( window_size:[1200, 630], timeout: 10 ) # Go to our special Rails view browser.goto(url) # Optional: Wait for any custom fonts or images to load browser.network.wait_for_idle # Take the screenshot and save it to a temporary file temp_file = Tempfile.new(['card', '.png']) browser.screenshot(path: temp_file.path, format: :png) browser.quit temp_file end
end
# app/models/post.rb
class Post < ApplicationRecord has_one_attached :og_image
end
# app/models/post.rb
class Post < ApplicationRecord has_one_attached :og_image
end
# app/models/post.rb
class Post < ApplicationRecord has_one_attached :og_image
end
# app/jobs/generate_og_image_job.rb
class GenerateOgImageJob < ApplicationJob queue_as :default def perform(post_id) post = Post.find(post_id) # We use Rails routing helpers to get the full URL url = Rails.application.routes.url_helpers.card_preview_post_url(post, host: 'https://myapp.com') # Run our Ferrum -weight: 500;">service file = CardGenerator.capture(url) # Attach the image to the post post.og_image.attach( io: File.open(file.path), filename: "og_image_#{post.id}.png", content_type: 'image/png' ) file.close file.unlink # Clean up the tempfile end
end
# app/jobs/generate_og_image_job.rb
class GenerateOgImageJob < ApplicationJob queue_as :default def perform(post_id) post = Post.find(post_id) # We use Rails routing helpers to get the full URL url = Rails.application.routes.url_helpers.card_preview_post_url(post, host: 'https://myapp.com') # Run our Ferrum -weight: 500;">service file = CardGenerator.capture(url) # Attach the image to the post post.og_image.attach( io: File.open(file.path), filename: "og_image_#{post.id}.png", content_type: 'image/png' ) file.close file.unlink # Clean up the tempfile end
end
# app/jobs/generate_og_image_job.rb
class GenerateOgImageJob < ApplicationJob queue_as :default def perform(post_id) post = Post.find(post_id) # We use Rails routing helpers to get the full URL url = Rails.application.routes.url_helpers.card_preview_post_url(post, host: 'https://myapp.com') # Run our Ferrum -weight: 500;">service file = CardGenerator.capture(url) # Attach the image to the post post.og_image.attach( io: File.open(file.path), filename: "og_image_#{post.id}.png", content_type: 'image/png' ) file.close file.unlink # Clean up the tempfile end
end
<!-- app/views/layouts/application.html.erb -->
<head> <!-- Other standard meta tags... --> <% if @post&.og_image&.attached? %> <meta property="og:image" content="<%= url_for(@post.og_image) %>"> <meta name="twitter:card" content="summary_large_image"> <% end %>
</head>
<!-- app/views/layouts/application.html.erb -->
<head> <!-- Other standard meta tags... --> <% if @post&.og_image&.attached? %> <meta property="og:image" content="<%= url_for(@post.og_image) %>"> <meta name="twitter:card" content="summary_large_image"> <% end %>
</head>
<!-- app/views/layouts/application.html.erb -->
<head> <!-- Other standard meta tags... --> <% if @post&.og_image&.attached? %> <meta property="og:image" content="<%= url_for(@post.og_image) %>"> <meta name="twitter:card" content="summary_large_image"> <% end %>
</head> - We created a 1200x630 HTML view.
- We used Ferrum to take a screenshot of that view.
- We used Solid Queue to run it in the background.
- We used ActiveStorage to save the file permanently.