<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vasyl Zubach</title>
    <description>The latest articles on DEV Community by Vasyl Zubach (@vasyl_zubach).</description>
    <link>https://dev.to/vasyl_zubach</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392810%2F614e3961-19ab-4355-aec6-27f45bdd3bc2.png</url>
      <title>DEV Community: Vasyl Zubach</title>
      <link>https://dev.to/vasyl_zubach</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vasyl_zubach"/>
    <language>en</language>
    <item>
      <title>Automate Open Graph image creation</title>
      <dc:creator>Vasyl Zubach</dc:creator>
      <pubDate>Tue, 13 Apr 2021 04:56:14 +0000</pubDate>
      <link>https://dev.to/vasyl_zubach/automate-open-graph-image-creation-3lf3</link>
      <guid>https://dev.to/vasyl_zubach/automate-open-graph-image-creation-3lf3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally posted on my personal website &lt;a href="https://www.zubach.com/blog/automate-open-graph-image-creation" rel="noopener noreferrer"&gt;https://www.zubach.com/blog/automate-open-graph-image-creation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are a developer, you've probably seen the Open Graph images (part of &lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;Open Graph Protocol&lt;/a&gt;) generated by popular dev related websites like &lt;a href="https://dev.to/"&gt;DEV.to&lt;/a&gt; or even &lt;a href="http://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;'s &lt;a href="https://og-image.vercel.app/" rel="noopener noreferrer"&gt;Open Graph Image as a Service&lt;/a&gt;. Both examples are using an approach to render image related to the content, so it contains some standard layout background, an image in it that is related to the content (vercel's logo or author's avatar), headline or title of the article, and a description.&lt;/p&gt;

&lt;p&gt;Here's how the Open Graph image looks like for my "10 Phone skins in CSS" article on DEV.to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bxa4iqbes3uy99oqmd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bxa4iqbes3uy99oqmd0.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both of those are very nice approaches and requires a little preparation for every website to make those images unique and their own. I wanted to generalise the idea a bit like a quick solution, or a first step, in case you need to add &lt;code&gt;og:image&lt;/code&gt;s quickly, and almost no cost in time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;While I consider this approach actually an ideal and the way it should be done, there are few things that could be considered for improvement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requires additional design + development approach to make it look as needed&lt;/li&gt;
&lt;li&gt;autogeneration of OG images for any kind of pages, not only the blogposts kind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How about solving these problems with a more generic approach that would suit all the needs?&lt;/p&gt;

&lt;h2&gt;
  
  
  Generic solution
&lt;/h2&gt;

&lt;p&gt;One of my wife's favourite sayings is "Start where you are, use what you have, do what you can" by &lt;a href="https://en.wikipedia.org/wiki/Arthur_Ashe" rel="noopener noreferrer"&gt;Arthur Ashe&lt;/a&gt;. So let's start with what we already have for all the pages we want to generate the Open Graph image - already designed and implemented web page that we can load;&lt;/p&gt;

&lt;p&gt;As we already have the page, let's just create an API that will return a screenshot of it of a specific size. Naturally, the most important information should be on that first viewable screen. &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; would be the go-to tool for that kind of work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the &lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;DevTools Protocol&lt;/a&gt;. Puppeteer runs &lt;a href="https://developers.google.com/web/updates/2017/04/headless-chrome" rel="noopener noreferrer"&gt;headless&lt;/a&gt; by default, but can be configured to run full (non-headless) Chrome or Chromium.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, we want to create an API that will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;grab whatever URL we provide;&lt;/li&gt;
&lt;li&gt;load that URL via Puppeteer and return an image for it;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the example below I used the approach that works for &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, but you should be able to use it for any kind of node.js backend or deploy this approach as a microservice using Vercel and proxy to it from your backend.&lt;/p&gt;

&lt;p&gt;For simplicity (and limitation to the size of the Serverless Function API in Vercel), let's create a separate project/microservice that will take care of OG image generation. It'll be just a subdomain, that will our main project in terms of URL, but return images instead of HTML. So, if we have our &lt;code&gt;https://example.com/&amp;lt;URL&amp;gt;&lt;/code&gt; website URL, the Open Graph image URL gonna be &lt;code&gt;https://og-image.example.com/&amp;lt;URL&amp;gt;&lt;/code&gt; (same URL, but on &lt;code&gt;og-image&lt;/code&gt; subdomain)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1
&lt;/h2&gt;

&lt;p&gt;According to Vercel's &lt;a href="https://vercel.com/docs/serverless-functions/introduction" rel="noopener noreferrer"&gt;Serverless Functions&lt;/a&gt; guide, let's create &lt;code&gt;api/index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// a chrome we need for Serverless Function API to use by puppeteer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome-aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// needed to be able to run local chromium to test how everything works locally&lt;/span&gt;
  &lt;span class="nx"&gt;WEBSITE&lt;/span&gt; &lt;span class="c1"&gt;// This is your main website URL&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// helper function just in case to give a page some time to render things after loading&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;websiteURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// FYI, it starts with `/`&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;WEBSITE&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;websiteURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headless&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/homebrew/bin/chromium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// based on `which chromium` command, I installed mine via homebrew&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// A bit of delay to make sure page is fully settled&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s-maxage=300, stale-while-revalidate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`image/jpeg`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As this is a Vercel API, we need to route all requests of our microservice to it by providing &lt;code&gt;vercel.json&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/index.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is it. This API will load the page, wait for all requests to finish (by &lt;code&gt;{ waitUntil: 'networkidle0' }&lt;/code&gt; instruction to puppeteer) and will pass along the screenshot of the size and quality we need as a response.&lt;/p&gt;

&lt;p&gt;After this one step, you can already use your automatically generated Open Graph image as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt;
  &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt;
  &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://og-image.example.com/whatever/url/you-wanna?pass=here"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 (optional)
&lt;/h2&gt;

&lt;p&gt;There's a little more we can do to improve this API performance. We know what services we are using, that might do the network calls, but they aren't that important to the outcome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some analytics&lt;/li&gt;
&lt;li&gt;tracking pixels&lt;/li&gt;
&lt;li&gt;social buttons&lt;/li&gt;
&lt;li&gt;comments service&lt;/li&gt;
&lt;li&gt;anything that if loading potentially at the very bottom of the page&lt;/li&gt;
&lt;li&gt;❗️and the most important - the self-reference to the Open Graph image of current page (so we don't end up having an infinite loop)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, theoretically, we could block those requests and make the loading a bit faster, as all we need is the image, not the fully functional website. Let's add some code right before opening the page line &lt;code&gt;await page.goto(fullUrl, { waitUntil: 'networkidle0' });&lt;/code&gt; to intercept requests and provide some guidelines to puppeteer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// to be able to intercept the requests:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestInterception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Ignore requests for resources that don't produce DOM&lt;/span&gt;
  &lt;span class="c1"&gt;// (images, stylesheets, media).&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;whitelist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xhr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stylesheet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;whitelist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Don't load your analytics lib of choise requests so pageviews aren't 2x.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blacklist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.google-analytics.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/gtag/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ga.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analytics.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disqus.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`og-image&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;websiteURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="c1"&gt;// self-reference I mentioned above&lt;/span&gt;
    &lt;span class="c1"&gt;// add more domains to ignore here&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;reqUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Pass through all other requests.&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 (optional)
&lt;/h2&gt;

&lt;p&gt;In order to use the same domain for OG images, I used a config in &lt;code&gt;vercel.json&lt;/code&gt; to route internal routes like &lt;code&gt;https://example.com/og-image/&amp;lt;any-url&amp;gt;&lt;/code&gt; to my &lt;code&gt;og-image&lt;/code&gt; microservice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"filesystem"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;one&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/og-image/(?&amp;lt;path&amp;gt;.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://og-image.example.com/$path"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;routes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;goes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;here&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;{ "handle": "filesystem" }&lt;/code&gt; config is specifically at the top, to handle the case where og images can be provided as files right away. If that's not your case - feel free to move the config for &lt;code&gt;og-image&lt;/code&gt; route to the top&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ways to improve/expand it
&lt;/h2&gt;

&lt;p&gt;There are definitely ways to improve and expand it. A very few that comes to mind are:&lt;/p&gt;

&lt;h3&gt;
  
  
  Combination of generic, and dedicated OG images
&lt;/h3&gt;

&lt;p&gt;For the blog posts, the look of OG image that DEV.to has is great. So, we could have this generic approach in place, as well as create a simple page that we should take screenshot for blosposes. Let's say we have blogpost URLs like &lt;code&gt;https://example.com/blog/url&lt;/code&gt;. Generic URL for making screenshot for it via microservice would be: &lt;code&gt;https://example.com/og-image/blog/url&lt;/code&gt;, but we could create specific tiny pages for articles like &lt;code&gt;https://example.com/preview/blog/url&lt;/code&gt; that would output exactly what we need to see on the OG image, but as a little webpage for it.&lt;/p&gt;

&lt;p&gt;This approach could be used for anything really: blogposts, reviews page, about pages, etc... Main idea is to gradually keep making those pages for smaller previews under similar URL location (like &lt;code&gt;https://example.com/preview/&amp;lt;any-url-here&amp;gt;&lt;/code&gt;), and then add &lt;code&gt;/og-image/&lt;/code&gt; prefix to those URLs to now use our microservice that generates the images from those previews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accept configuration
&lt;/h3&gt;

&lt;p&gt;A great improvement would be to provide some configs right from the URL by &lt;code&gt;GET&lt;/code&gt; params instead of some env variables. Like: &lt;code&gt;?_w=1000&amp;amp;_h=800&amp;amp;_q=81&amp;amp;_t=jpeg&lt;/code&gt; (&lt;code&gt;_w&lt;/code&gt; for width, &lt;code&gt;_h&lt;/code&gt; - height, &lt;code&gt;_q&lt;/code&gt; - quality, &lt;code&gt;_t&lt;/code&gt; - type). There is a possibility to overlap with some of the actual GET parameters in the URL, so here I used &lt;code&gt;_&lt;/code&gt; to make it more unique, and "private" in the JavaScript sense.&lt;/p&gt;

&lt;p&gt;The reason this would be a great improvement is because there could be multiple OG images &lt;code&gt;&amp;lt;meta /&amp;gt;&lt;/code&gt; tags on the page, with different sizes for different purposes. As different social networks that are using those could use different sizes for their needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's how this blogpost Open Graph image looks like on my website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zubach.com%2Fog-image%2Fblog%2Fautomate-open-graph-image-creation" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zubach.com%2Fog-image%2Fblog%2Fautomate-open-graph-image-creation" alt="Automate Open Graph image creation OG image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;and the fact that this image is loaded, means that self-reference fix we did in "Step 2" works&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Links to useful services and tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;Open Graph Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vercel's &lt;a href="https://og-image.vercel.app/" rel="noopener noreferrer"&gt;Open Graph Image as a Service&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/vasyl_zubach/10-phone-skins-in-css-2lfe"&gt;10 Phone skins in CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.facebook.com/tools/debug" rel="noopener noreferrer"&gt;Facebook Sharing Debugger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>puppeteer</category>
    </item>
    <item>
      <title>10 Phone skins in CSS</title>
      <dc:creator>Vasyl Zubach</dc:creator>
      <pubDate>Sat, 13 Jun 2020 08:28:08 +0000</pubDate>
      <link>https://dev.to/vasyl_zubach/10-phone-skins-in-css-2lfe</link>
      <guid>https://dev.to/vasyl_zubach/10-phone-skins-in-css-2lfe</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLdULEEb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6szjz96pz5tn59oqnb0z.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLdULEEb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6szjz96pz5tn59oqnb0z.jpeg" alt="Phones in CSS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A while ago after I did an &lt;a href="https://zubach.com/iphone"&gt;iPhone&lt;/a&gt; and &lt;a href="https://zubach.com/lumia"&gt;Lumia&lt;/a&gt; in CSS and a touch of JavaScript (for basic functionality like &lt;code&gt;lock&lt;/code&gt; &amp;amp; &lt;code&gt;unlock&lt;/code&gt; the device), I was contacted by one company to re-create 10 specific android skins in CSS. I did the order quickly in my spare time but never had a chance to share them publicly.&lt;/p&gt;

&lt;p&gt;Now I guess is a time, ~8 years after the work was done 🤪&lt;/p&gt;

&lt;p&gt;So, here's the list:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can check the entire collection page on Codepen - &lt;a href="https://codepen.io/collection/nVyxOk"&gt;Imageless Devices in CSS&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Default Android Device Skin 4"
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/MWKyBeP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Galaxy S4
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/vYLGzQV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Galaxy S3
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/rNxeZPy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Galaxy Note 3
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/OJMNoqe?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Nexus 5
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/KKVzxLz?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  HTC One
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/dyGMqEj?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Sony Xperia Z1
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/jOWqvjW?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Lenovo VIBE X
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/ExPKeBb?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Ascend Mate 2
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/gOPrdNJ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Xiaomi Mi3
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/vasyl-zubach/embed/yLeOxmV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Which one do you like more? ;)&lt;/p&gt;

</description>
      <category>css</category>
      <category>html</category>
      <category>imageless</category>
      <category>purecss</category>
    </item>
  </channel>
</rss>
