Hey hey heyyyyyy! Here’s a quick article on how to have beauty link previews, just like Dev.to does.
You're gonna get something like this:
Yup, was being literal with the "like dev.to" 😛 I've built something similar for my new project workby.io, and here's how I did it...
Almost every guide that I found was using htmlcsstoimage.com, in fact, dev.to uses it. It's pretty popular and efficient, BUT, I don't want to pay $14 bucks for each 1k image 😂🐁
So, I'm going to use Grover: A ruby gem to transform HTML into PDFs, PNGs, or JPEGs using Google Puppeteer and Chromium.
If you want to build this with, let's say, Node.js, you can also follow me. Puppeteer is popular and easy to use.
Long story short: We're going to build an HTML view and take a screenshot of it.
You can create a blank Rails project, or just add additional functionality to your current app.
Install Grover gem with
bundle add grover
And the dependency of Grover, Puppeteer, with:
yarn add puppeteer
Then create an empty controller where you can respond with different link preview templates:
rails g controller og-imager dev_to
In the dev_to
action, let's call Grover and let him do the magic
app/controllers/og_imager_controller.rb
class OgImagerController < ApplicationController
# GET /og_imager/dev_to
# @param {string} title
# @param {string} avatar
# @param {string} username
# @param {string} timestamp
# @param {array} logos
# @returns image/png
def dev_to
# Get params and set to variable
# TODO: Retrieve your object instead :p
@title = params[:title]
@avatar = params[:avatar]
@username = params[:username]
@timestamp = params[:timestamp]
@logos = params[:logos]
# Grover.new accepts a URL or inline HTML and optional parameters for Puppeteer
grover = Grover.new(
render_to_string
)
# Get a screenshot
png = grover.to_png
# Render image
send_data(png, type: 'image/png', disposition: 'inline')
end
end
Here's our view, just simple HTML/CSS
app/views/og_imager/dev_to.html.erb
<div class="container">
<div class="card">
<h1 class="title">
<%= @title %>
</h1>
<div class="details">
<div class="user">
<img src="<%= @avatar %>" class="avatar">
<p class="username">
<%= @username %> - <%= @timestamp %>
</p>
</div>
<div class="logos">
<% @logos.each do |logo| %>
<img src="<%= logo %>" class="logo">
<% end %>
</div>
</div>
</div>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap');
*{
font-family: 'Roboto', sans-serif;
color: #cd685f;
}
.container{
width: 1128px;
height: 600px;
display: flex;
}
.card{
display: flex;
justify-content: space-between;
flex-direction: column;
margin: 2.8rem 4rem 4rem 2.8rem;
border: 3px solid #c56b61;
border-radius: 25px 25px 0px 0px;
box-shadow: 7px 10px 0px 1px #c56b61;
padding: 3.8rem 1.8rem 1.8rem 1.8rem;
}
.title{
margin: 0px;
font-weight: 500;
font-size: 5rem;
line-height: 80px;
}
.details{
display: flex;
justify-content: space-between;
}
.user{
display: flex;
align-items: center;
}
.avatar{
border: 3px solid #c56b61;
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.username{
margin: 0;
font-size: 2.5rem;
}
.logos{
display: flex;
justify-content: space-between;
}
.logo{
width: 60px;
height: 60px;
margin-left: 20px;
transform: rotate(5deg);
}
</style>
And finally, create an initializer to set our Browser viewport (final image size).
initializers/grover.rb
Grover.configure do |config|
config.options = {
viewport: {
width: 1128,
height: 600
},
}
end
If you have created a blank project, and want to use this as a micro-service, here's a guide on how to deploy this thing to Heroku.
heroku create your_app_name
heroku buildpacks:add heroku/nodejs --index=1
heroku buildpacks:add jontewks/puppeteer --index=2
heroku config:set GROVER_NO_SANDBOX=true
To make use of this image on the link preview, you will need The Open Graph protocol. Just simple meta tags that browsers will look for.
Meta tags are always on <head> so,
app/views/layouts/application.html.erb
<%= yield(:head) %>
And then ask ERB to fill the previous block with
<%= content_for :head do %>
<meta property="og:title" content="<%= @article.title %>">
<meta property="og:image" content="<%= article_link_preview(@article) %>">
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1128">
<meta property="og:image:height" content="600">
<meta name="twitter:card" content="summary_large_image" />
<% end %>
at your details view. Like: /articles/:id
The twitter:card
thing it's because they are fancier on link previews, you can read more about that here
The previous article_link_preview
method it's a simple helper that generates the URL that we are looking for.
app/helpers/articles_helper.rb
def article_link_preview article
uri = URI.parse('https://your_app_name.herokuapp.com/ogimage')
uri.query = URI.encode_www_form(
title: article.title,
'images[]': article.tags.collect(&:image),
timestamp: article.created_at.to_formatted_s(:short),
...
)
uri.to_s
end
You will get something like this:
https://your_app_name.herokuapp.com/og_imager/dev_to?title=Real%20Time%20Notification%20System%20with%20Hotwire,%20in%20Rails%207&avatar=https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/369241/a0111bcb-a046-4398-98cd-f4cf0e2f8d0d.png&username=Matias%20Carpintini×tamp=13%20April&logos[]=https://img.icons8.com/office/80/000000/ruby-gemstone.png&logos[]=https://practicaldev-herokuapp-com.freetls.fastly.net/assets/devlogo-pwa-512.png
Here's the repo of the whole thing.
This article was first published here.