<?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: Dennis Hagemeier</title>
    <description>The latest articles on DEV Community by Dennis Hagemeier (@dennisview).</description>
    <link>https://dev.to/dennisview</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%2F274983%2F464cd097-5e6e-4e46-84d7-6f0c4f870409.jpg</url>
      <title>DEV Community: Dennis Hagemeier</title>
      <link>https://dev.to/dennisview</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dennisview"/>
    <language>en</language>
    <item>
      <title>Generate Social Media Preview Images</title>
      <dc:creator>Dennis Hagemeier</dc:creator>
      <pubDate>Wed, 18 Dec 2019 18:07:32 +0000</pubDate>
      <link>https://dev.to/dennisview/generate-social-media-preview-images-41f0</link>
      <guid>https://dev.to/dennisview/generate-social-media-preview-images-41f0</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LN6i6omo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ag9f06cfZLNsiYU2nQ_dsww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LN6i6omo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ag9f06cfZLNsiYU2nQ_dsww.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published&lt;/em&gt; &lt;a href="https://www.d-hagemeier.com/en/articles/generate-social-media-preview-images/"&gt;&lt;em&gt;on my personal website&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Why Social Media Preview Images?
&lt;/h4&gt;

&lt;p&gt;You wrote a great blog entry. You share it on Twitter, WhatsApp or Facebook. And you want your audience to notice the blog entry and click.&lt;/p&gt;

&lt;p&gt;Presentation is the key. First thing that catches the eye is not your well-formulated tweet, but the preview image.&lt;/p&gt;

&lt;p&gt;Without optimization, a tweet looks like this example from Gatsby:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P_lYhwy2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/597/0%2ALfp2RFZm_yt1QXsN.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P_lYhwy2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/597/0%2ALfp2RFZm_yt1QXsN.jpg" alt="Small Twitter preview image"&gt;&lt;/a&gt;Small Twitter preview image&lt;/p&gt;

&lt;p&gt;With a matching preview image, the Tweet is much more present:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4uk38-R2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/596/0%2ApLrBupEIDx37VXK9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4uk38-R2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/596/0%2ApLrBupEIDx37VXK9.jpg" alt="Big Twitter preview image"&gt;&lt;/a&gt;Big Twitter preview image&lt;/p&gt;

&lt;p&gt;A normal person would now open Photoshop, create a template file and save the image for the post. But it would be… boring. So I use NodeJS, &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; and automate the whole thing 😄&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For this example I use the SSG &lt;a href="https://www.11ty.dev/"&gt;11ty&lt;/a&gt;, but the functionality is not limited to a CMS. You can also generate and process a template file with WordPress.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Generate HTML template
&lt;/h4&gt;

&lt;p&gt;My first approach to creating thumbnails was to generate SVGs. A basic design in SVG, changing variables like title or URL dynamically, converting to PNG or JPG and — fiddlesticks. Because SVGs &lt;a href="https://wiki.selfhtml.org/wiki/SVG/Tutorials/mehrzeiliger_Text"&gt;fail with multiline text&lt;/a&gt;. At the latest with longer headings this becomes a real problem.&lt;/p&gt;

&lt;p&gt;Instead, an HTML template forms the basis. As already mentioned I use 11ty, for this I combine Nunjucks as template language. With the help of a pagination I then generate an additional thumbnail HTML for each regular HTML page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I chose 1200x628 pixels as dimensions, so the thumbnail fits perfectly for Facebook, Twitter and WhatsApp. If you create thumbnails for other platforms, you can find the current dimensions at &lt;a href="https://sproutsocial.com/insights/social-media-image-sizes-guide/"&gt;Sprout Social&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;---
pagination:
 data: collections.all
 size: 1
 alias: preview
permalink: "/assets/preview-images/{{ preview.data.title | pslug }}-{{ preview.data.language | url }}-preview.html"
eleventyExcludeFromCollections: true
---
\&lt;span class="cp"&gt;&amp;lt;!doctype html\&amp;gt;&lt;/span&gt;
\&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

\&lt;span class="nt"&gt;&amp;lt;head&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 \&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 \&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 \&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"ie=edge"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 \&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"robots"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"noindex, nofollow"&lt;/span&gt; &lt;span class="err"&gt;/\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 \&lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;CSS&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"preview"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="nt"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="nt"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"80"&lt;/span&gt; &lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"91"&lt;/span&gt; &lt;span class="nt"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"0 0 441 500"&lt;/span&gt; &lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"logo"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="nt"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"M386.9 311.7c7.7-44 27-82.8 54-113.8C425.2 66 337.2 0 177.2 0H0v500h177.2c80.7 0 145.3-23.2 193.7-69.7 6.9-6.7 13.4-13.7 19.3-21-7.6-30.8-9.2-64-3.3-97.6zm-103.5 53c-27.8 29.3-66.1 43.9-114.9 43.9h-55.8V91.7h55.1c49.7 0 88.4 13.7 116 41C311.3 160 325 197.5 325 245.1c0 50.5-13.9 90.3-41.6 119.6z"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="err"&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="err"&gt;{&lt;/span&gt; &lt;span class="err"&gt;preview.data.title&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;}\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;www&lt;/span&gt;&lt;span class="nc"&gt;.d-hagemeier.com&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.d-hagemeier.com/assets/preview-images/hi-im-dennis-en-preview.html"&gt;Example of a generated file&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exclude the crawlers from Google &amp;amp; Co. via “nofollow” so that your HTML preview templates do not appear in search engines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Generate JSON with the required data
&lt;/h4&gt;

&lt;p&gt;To pass the HTML templates to the image generator later, you next create a list of all HTML templates and their paths. Here is an excerpt from &lt;a href="https://www.d-hagemeier.com/preview-images.json"&gt;my JSON file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
 {
 "filename" : "import-tweets-from-twitter-api-in-11ty-en-preview",
 "path" : "https://www.d-hagemeier.com/assets/preview-images/import-tweets-from-twitter-api-in-11ty-en-preview.html"
 },{
 "filename" : "from-wordpress-to-jamstack-en-preview",
 "path" : "https://www.d-hagemeier.com/assets/preview-images/from-wordpress-to-jamstack-en-preview.html"
 },{
 "filename" : "5-tips-you-can-learn-in-las-vegas-for-your-business-en-preview",
 "path" : "https://www.d-hagemeier.com/assets/preview-images/5-tips-you-can-learn-in-las-vegas-for-your-business-en-preview.html"
 }
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Create Google Storage
&lt;/h4&gt;

&lt;p&gt;Netlify has a big disadvantage: With every deploy the old data is deleted. There are tricks with undocumented cache directories, but I didn’t want to rely on them.&lt;/p&gt;

&lt;p&gt;Netlify would normaly delete and recreate all image data for each deploy. Depending on how many blog articles you write and how many images are generated, this generates a lot of work.&lt;/p&gt;

&lt;p&gt;Instead, I decided to store the thumbnails in the &lt;a href="https://cloud.google.com/storage/"&gt;Google Storage&lt;/a&gt;. Google Storage belongs to the Google Cloud Platform, offers the storage of data in so-called buckets and is free of charge for the first 12 months.&lt;/p&gt;

&lt;p&gt;The creation of a suitable bucket is easy after login, I have attached my personal settings in brackets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“Create a bucket”&lt;/li&gt;
&lt;li&gt;Give a name (“previewimages”)&lt;/li&gt;
&lt;li&gt;Select storage location (“Multi-region”, “eu”)&lt;/li&gt;
&lt;li&gt;Select memory class (“Standard”)&lt;/li&gt;
&lt;li&gt;Set up access control (“detailed”)&lt;/li&gt;
&lt;li&gt;Advanced settings (all set to default)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the settings are done, your new bucket is waiting for you and you could already upload files manually.&lt;/p&gt;

&lt;p&gt;To let our script store files in the bucket later, we need the corresponding Google Credentials. Just follow the &lt;a href="https://console.cloud.google.com/apis/credentials/serviceaccountkey"&gt;official Google instructions&lt;/a&gt; and create a new service account. You will then receive a JSON file with your access keys. Save these keys well, they will only be generated once per service account!&lt;/p&gt;

&lt;p&gt;Save the values CLOUD_PROJECT_ID, BUCKET_NAME, CLIENT_EMAIL and PRIVATE_KEY as .env variables, so they do not appear publicly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Packages and settings
&lt;/h4&gt;

&lt;p&gt;Time for our actual script, in my case I called this previewimages.js. First you add the required NPM packages…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add axios puppeteer @google-cloud/storage dotenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;…and register them in the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const axios = require('axios');
const puppeteer = require('puppeteer');
const { Storage } = require('@google-cloud/storage');
require('dotenv').config()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, add your variables.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the error error:0906D06C:PEM routines:PEM_read_bio:no start line appears in Netlify later, my solution was to encode PRIVATEKEY to base64. In the local development environment I had no problems, it seems to be due to the multi-line format of the key.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const GOOGLE\_CLOUD\_PROJECT\_ID = process.env.GOOGLE\_CLOUD\_PROJECT\_ID;
const BUCKET\_NAME = process.env.GOOGLE\_BUCKET\_NAME;
const CLIENTEMAIL = process.env.GOOGLE\_CLIENT\_EMAIL;

// If you have encoded your private key using base64:
const PRIVATEKEY = Buffer.from(process.env.GOOGLE\_PRIVATE\_KEY, 'base64').toString();
// If not:
const PRIVATEKEY = process.env.GOOGLE\_PRIVATE\_KEY;

const credentials = {
 client\_email : CLIENTEMAIL,
 private\_key : PRIVATEKEY
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And last but not least you deposit the basic settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const settings = {
 source: "https://PATH-TO-YOUR-JSON-FILE.json",
 imgwidth: 1200,
 imgheight: 628
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Axios data processing
&lt;/h4&gt;

&lt;p&gt;First, you load your JSON file via Axios and pass the data to your processing function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;axios.get(settings.source)
 .then((response) =\&amp;gt; {
 setupGoogleStorage(response.data);
 })
 .catch((err) =\&amp;gt; {
 console.log('Error Axios: ', err)
 });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Google storage function
&lt;/h4&gt;

&lt;p&gt;To prevent existing thumbnails from being recreated, first check which images are already stored in the bucket.&lt;/p&gt;

&lt;p&gt;Create a new function setupGoogleStorage and authorize access to your bucket. Then we loop through the HTML template links and check via file.exists() if the image is available.&lt;/p&gt;

&lt;p&gt;If the picture exists, only a short message appears in the console. If it has to be created, you pass the path, file and filename to the get function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight hack"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setupGoogleStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

 &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE\_CLOUD\_PROJECT\_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUCKET\_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nx"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nx"&gt;let&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&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="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Image already exists: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Error setupGoogleStorage: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Make screenshots
&lt;/h4&gt;

&lt;p&gt;Now you actualy take the screenshots. In the get function we start a new puppeteer page and request the screenshot via the getscreen function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight hack"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;headless&lt;/span&gt;&lt;span class="o"&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="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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;buffer&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;getscreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&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;uploadBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Uploaded: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makePublic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getscreen&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="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Getting: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;url&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imgwidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imgheight&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;waitUntil&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'networkidle0'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;buffer&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Got: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&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;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Error getscreen:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;With networkidle0 Puppeteer waits until there is no more network activity within 500ms. This delays the creation, but also ensures that all CSS files, fonts and images are loaded correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Puppeteer don’t got variables for pagescreenshot in getscreen and saves the screenshot only as a buffer. Now pass this buffer to the Google Bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight hack"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&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;=&lt;/span&gt;&lt;span class="err"&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="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&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="nx"&gt;resolve&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Finished previewimages.js
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight hack"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'axios'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'puppeteer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Storage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@google-cloud/storage'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fs'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dotenv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;GOOGLE&lt;/span&gt;&lt;span class="nx"&gt;\_CLOUD\_PROJECT\_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE\_CLOUD\_PROJECT\_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;BUCKET&lt;/span&gt;&lt;span class="nx"&gt;\_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE\_BUCKET\_NAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;CLIENTEMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE\_CLIENT\_EMAIL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;PRIVATEKEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE\_PRIVATE\_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;client\_email&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CLIENTEMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;private\_key&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PRIVATEKEY&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://PATH-TO-YOUR-JSON-FILE.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;imgwidth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;imgheight&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;628&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setupGoogleStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

 &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE\_CLOUD\_PROJECT\_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUCKET\_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nx"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nx"&gt;let&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&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="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Image already exists: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Error setupGoogleStorage: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;headless&lt;/span&gt;&lt;span class="o"&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="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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;buffer&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;getscreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&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;uploadBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Uploaded: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makePublic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getscreen&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="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Getting: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;url&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imgwidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imgheight&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;waitUntil&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'networkidle0'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;buffer&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="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Got: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;".png"&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;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Error getscreen:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&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;=&lt;/span&gt;&lt;span class="err"&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="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&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="nx"&gt;resolve&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&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="nx"&gt;setupGoogleStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&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="nx"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Error Axios: '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;h4&gt;
  
  
  Embedding as metatag + verification on Twitter
&lt;/h4&gt;

&lt;p&gt;You need the appropriate metatags to display social media preview images. There are the general Open-Graph-Tags and the Twitter-Tags, both belong to &amp;lt;head&amp;gt; of your website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;meta property="og:image" content="https://URL-TO-YOUR-PREVIEW-IMAGE.png" /\&amp;gt;
\&amp;lt;meta property="og:image:height" content="1200" /\&amp;gt;
\&amp;lt;meta property="og:image:width" content="628" /\&amp;gt;
\&amp;lt;meta property="og:image:alt" content="ALT TEXT FOR YOUR PREVIEW IMAGE" /\&amp;gt;

\&amp;lt;meta name="twitter:image" content="https://URL-TO-YOUR-PREVIEW-IMAGE.png" /\&amp;gt;
\&amp;lt;meta property="twitter:image:alt" content="ALT TEXT FOR YOUR PREVIEW IMAGE" /\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The URL for your image is &lt;a href="https://storage.cloud.google.com/YOUR_BUCKETNAME/IMAGENAME.png."&gt;https://storage.cloud.google.com/YOUR_BUCKETNAME/IMAGENAME.png.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order for your large image to appear on Twitter, you also need to add an additional specification…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;meta name="twitter:card" content="summary\_large\_image" /\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;…and test the result in the &lt;a href="https://cards-dev.twitter.com/validator"&gt;Validator&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H6ReFMUQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1004/0%2AXofhdB9ajwlbOlRF.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H6ReFMUQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1004/0%2AXofhdB9ajwlbOlRF.jpg" alt="Twitter Card validator"&gt;&lt;/a&gt;Twitter Card validator&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy with new article
&lt;/h4&gt;

&lt;p&gt;In order for each new article to receive a preview image directly, all you have to do is specify when the deploy should start. My own workflow for that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Website sends a webhook (“Outgoing webhook” in Netlify, under “Deploy notifications”) when a new deploy is launched&lt;/li&gt;
&lt;li&gt;“Build hook” of the preview page in Netlify triggers new deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don’t use Netlify, you can trigger the webhook differently. For example, if you’d like to trigger a deploy on every new article in WordPress, then add one of the automatically generated RSS feeds to &lt;a href="https://ifttt.com/"&gt;ifttt.com&lt;/a&gt; with the action “Webhook” and the Webhook target of your preview page.&lt;/p&gt;

&lt;p&gt;That’s it, happy previewing! 😄&lt;/p&gt;

</description>
      <category>twitter</category>
      <category>javascript</category>
      <category>node</category>
      <category>opengraph</category>
    </item>
    <item>
      <title>Cookie-free Google Analytics</title>
      <dc:creator>Dennis Hagemeier</dc:creator>
      <pubDate>Tue, 19 Nov 2019 10:37:09 +0000</pubDate>
      <link>https://dev.to/dennisview/cookie-free-google-analytics-23gj</link>
      <guid>https://dev.to/dennisview/cookie-free-google-analytics-23gj</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MbOO8Vnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A4JOn3aoKA1PgScyznQL1Hw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MbOO8Vnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A4JOn3aoKA1PgScyznQL1Hw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the start of the GDPR I am looking for a cookie-free Google Analytics alternative. I am interested in which pages of my blog are accessed, where the visitors come from and, since the relaunch of my blog, what language they speak. I would also like to be able to view the data in aggregated form, for example as a simple dashboard. And since my blog is only my private pleasure, I don’t want to pay a lot of money every month for the analysis data. All this is easy to do with Google Analytics — if it weren’t for the GDPR.&lt;/p&gt;

&lt;h4&gt;
  
  
  The problem
&lt;/h4&gt;

&lt;p&gt;If I want to use Google Analytics in compliance with data protection regulations, I need the consent of the visitors. So I slap a banner in the face of a new user, “agree” or “disagree” as options. Only to then give his data to Google and make him traceable on numerous websites.&lt;/p&gt;

&lt;p&gt;Another disadvantage: More and more people are using extensions like Ghostery to block Google Analytics directly when they visit a page. At the latest with the further development of the browsers (like the Intelligent Tracking Prevention in WebKit) the data becomes less and less reliable.&lt;/p&gt;

&lt;p&gt;There are some alternatives to Google Analytics: Alternatives such as &lt;a href="https://simpleanalytics.com/"&gt;Simple Analytics&lt;/a&gt;, plugins such as &lt;a href="https://de.wordpress.org/plugins/statify/"&gt;Statify&lt;/a&gt; or switching to server log analysis tools, e.g. with &lt;a href="https://www.netlify.com/products/analytics/"&gt;Netlify Analytics&lt;/a&gt;. Most of them, however, cost money and limit me to the data collected with the corresponding tool. I’m a big fan of the Google Data Studio, so that I can also evaluate data from the Search Console and other sources at the same time.&lt;/p&gt;

&lt;p&gt;I’ve even done experiments with Firestore. With my own tracking pixel I have stored the user data there — but then I have to build EVERYTHING by myself. From the simple bot filter to the sorting of sources into channels to the interface with the Data Studio.&lt;/p&gt;

&lt;p&gt;So I want Google Analytics — only without cookies and GDPR-compliant.&lt;/p&gt;

&lt;h4&gt;
  
  
  The solution: Cookie-free Google Analytics
&lt;/h4&gt;

&lt;p&gt;With Google’s standard tracking code, cookie-free tracking is not possible. The script automatically sets multiple cookies for user ID, timestamp or jump tracking.&lt;/p&gt;

&lt;p&gt;So I use the second variant to import data to Google Analytics: The Measurement Protocol. Behind it nothing else hides than an interface for the transmission of raw data as HTTP request. If you want to play with the Measurement Protocol, you can go with the &lt;a href="https://ga-dev-tools.appspot.com/hit-builder/"&gt;Hit Builder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The solution of the cookie-free Google Analytics script therefore consists in a POST request in which we pack a basic set of user data and pass it on to the Measurement Protocol. I decided to use this data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User ID (Google requirement for a valid POST request)&lt;/li&gt;
&lt;li&gt;User agent (for filtering bot traffic)&lt;/li&gt;
&lt;li&gt;Page title&lt;/li&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;li&gt;Referrer&lt;/li&gt;
&lt;li&gt;Set language&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way of tracking also has some disadvantages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracking is limited to page views.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To save the complete browse history of a user, I would have to assign several page views to one user. Also works with the Measurement Protocol — but I would have to store a user ID as a cookie or in LocalStorage to keep the variable constant over several page views. However, this contradicts my basic idea of completely doing without cookies (and also without LocalStorage).&lt;/p&gt;

&lt;p&gt;Instead, each page view generates a new user ID and starts a new session with every page change. This means: No page flow, no number of users (this is displayed, but equal to the page views), no entries or jumps. Every pageview is a new entry and every page change means a jump.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No target group analysis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m just transmitting a basic framework of information. Since I don’t give Google enough information to chain several pageviews together and therefore no cross-page tracking is possible, I don’t have any information about the target group. Means: No demographic data, no location, no interests and no network information.&lt;/p&gt;

&lt;p&gt;However, I transfer the browser language used and the user agent also provides data on the device category and the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The referrer is transmitted correctly only for the first hit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since every pageview means a new session, the referrer is only set correctly for the first hit of a user. For all further calls the website itself is the referrer, so for every further pageview the source is set as “direct”.&lt;/p&gt;

&lt;p&gt;An example: If a user finds my blog via Google and looks at the three pages “Articles”, “Home” and “About”, the sources would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Articles: google/organic&lt;/li&gt;
&lt;li&gt;Home: direct&lt;/li&gt;
&lt;li&gt;About: direct&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;More detailed tracking is only possible in a very cumbersome way&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Although events can also be transmitted via the Measurement Protocol, this option would be very cumbersome. If you depend on detailed tracking, you are wrong with this solution and should rather use the Google Tag Manager with customized triggers and events.&lt;/p&gt;

&lt;h4&gt;
  
  
  Client side script
&lt;/h4&gt;

&lt;p&gt;If I would send the data directly to Google via POST-Request, Google would get a lot of information in the body of the request. Also, many privacy browser extensions would block this request.&lt;/p&gt;

&lt;p&gt;So I split the POST request and use a client side script and a server side script (the second as a proxy script).&lt;/p&gt;

&lt;h4&gt;
  
  
  Variables used
&lt;/h4&gt;

&lt;p&gt;First, I fill my client side file with the variables that I want to transfer later via POST request:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google uses for the User-ID (later cid) a &lt;a href="https://wikipedia.org/wiki/Universally_Unique_Identifier"&gt;Universally Unique Identifier&lt;/a&gt;. This can be generated with the following function and saved as a constant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function uuidv4() { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, \*c\* =\&amp;gt; (c ^ crypto.getRandomValues(new Uint8Array(1))[0] &amp;amp; 15 \&amp;gt;\&amp;gt; c / 4).toString(16)); }
const uuid = uuidv4();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Useragent&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const useragent = navigator.userAgent;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Seitentitel&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const title = document.title;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;URL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A regular URL consists of three parts: The domain (“&lt;a href="https://www.d-hagemeier.com%E2%80%9D"&gt;https://www.d-hagemeier.com”&lt;/a&gt;), the path (“/en/articles/”) and the parameters (“?utm_source=facebook&amp;amp;utm_medium=social”). I deliberately transmit them separately from each other so that I can access them individually later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Domain
const origin = window.location.origin;
// Path
const pathname = window.location.pathname;
// Parameter
const search = window.location.search;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Referrer&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const referrer = document.referrer;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Set language&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const language = navigator.language || navigator.userLanguage;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For simplicity’s sake, I then sum the data into a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const sitedata = {
 uuid: uuid,
 useragent: useragent,
 title: title,
 origin: origin,
 pathname: pathname,
 search: search,
 referrer: referrer,
 language: language
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Post request
&lt;/h4&gt;

&lt;p&gt;I use the fetch-API for the transmission as POST request. My endpoint for this is /.netlify/functions/send, because I use an AWS lambda function over Netlify for my server-side script. But this is not a must; for example a PHP endpoint would be possible, which passes the data to the measurement protocol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function senddata(data) {
 return fetch('/.netlify/functions/send', {
 body: JSON.stringify(data),
 method: 'POST'
 })
}

senddata(sitedata).catch((error) =\&amp;gt; {
 console.log('Error: ', error)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Server side script
&lt;/h4&gt;

&lt;p&gt;As already described, I use a Netlify function on the server side.&lt;/p&gt;

&lt;p&gt;I start with the query whether the call is a POST request — and forbid all other calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (event.httpMethod !== "POST") {
 return { statusCode: 405, body: "Method Not Allowed" };
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next I parse the transmitted files and save them in the constant data. Then I construct my payload and the endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const url = data.origin + data.pathname + data.search;
const endpoint = "https://www.google-analytics.com/collect";
const payload = encodeURI(`v=1&amp;amp;t=pageview&amp;amp;tid=UA-85526167-1&amp;amp;cid=${data.uuid}&amp;amp;ua=${data.useragent}&amp;amp;aip=1&amp;amp;ds=web&amp;amp;dl=${url}&amp;amp;dt=${data.title}&amp;amp;ul=${data.language}&amp;amp;dr=${data.referrer}`).replace(/\//g, '%2F');
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now the actual POST request to Google follows. I output the status as console.log or console.error, both are displayed in the Netlify dashboard under the functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
 const response = await fetch(`${endpoint}?${payload}`, {
 method: 'POST',
 cache: 'no-cache'
 })
 if (response.ok) {
 console.log(`Status ${response.status}: ${response.statusText}`)
 }
} catch (err) {
 console.error("Error: " + err)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Attention&lt;/strong&gt; : To use the fetch-API in NodeJS, I need the corresponding &lt;a href="https://www.npmjs.com/package/node-fetch"&gt;module&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The complete variants of the &lt;a href="https://github.com/dennishagemeier/d-hagemeier/blob/master/src/assets/js/send.js"&gt;Client side&lt;/a&gt; and &lt;a href="https://github.com/dennishagemeier/d-hagemeier/blob/master/functions/send/send.js"&gt;Server side scripts&lt;/a&gt; can be found in my Github-Repository.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bonus: Data Studio Template
&lt;/h4&gt;

&lt;p&gt;With these two files, the data is already exported to Google Analytics and can be filtered and evaluated there as usual. But I’m a fan of Google Data Studio, so I created a dashboard with the transferred data and the search queries from the Search Console.&lt;/p&gt;

&lt;p&gt;You can view and copy the Data Studio &lt;a href="https://datastudio.google.com/s/nNr0l5Et0PM"&gt;here&lt;/a&gt;. Just swap the data sources and you have a good starting point for your own Data Studio.&lt;/p&gt;

&lt;p&gt;First published on: &lt;a href="https://www.d-hagemeier.com/en/articles/cookie-free-google-analytics/"&gt;https://www.d-hagemeier.com/en/articles/cookie-free-google-analytics/&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gdpr</category>
      <category>cookies</category>
      <category>javascript</category>
      <category>googleanalytics</category>
    </item>
    <item>
      <title>Import Tweets from Twitter API in 11ty</title>
      <dc:creator>Dennis Hagemeier</dc:creator>
      <pubDate>Wed, 16 Oct 2019 16:30:00 +0000</pubDate>
      <link>https://dev.to/dennisview/import-tweets-from-twitter-api-in-11ty-3m3g</link>
      <guid>https://dev.to/dennisview/import-tweets-from-twitter-api-in-11ty-3m3g</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fhJ7g1AL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AShBWnQspxjFPYvCqCt2VDQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fhJ7g1AL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AShBWnQspxjFPYvCqCt2VDQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learn to retrieve your own tweets via API and dynamically display them in the Static Site Generator Eleventy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;Twitter is a wonderful platform to discover exciting articles, exchange ideas with people and pass on knowledge. So why not keep your website fresh with new tweets on the home page?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possibility 1:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Integrate the Twitter timeline via widget. Disadvantages: Little or no influence to design, an additional third-party script and the associated loss of page speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possibility 2:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Save the Twitter timeline directly in HTML via API in the build process. How this works with the Static Site Generator Eleventy is shown in this article.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create API access data
&lt;/h3&gt;

&lt;p&gt;In order to access the Twitter API, you first need your personal access data. Call the &lt;a href="https://developer.twitter.com/en/apps"&gt;Twitter-Developer-Portal&lt;/a&gt; and select Create an app. Most of the fields shown here don't even need to be filled in because the user of your website will never come into direct contact with the API.&lt;/p&gt;

&lt;p&gt;In my case the whole thing looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K8EXZ0Q6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AmeArCdEEG01bnrFN.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K8EXZ0Q6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AmeArCdEEG01bnrFN.jpg" alt="Twitter Developer App Overview"&gt;&lt;/a&gt;&lt;em&gt;Twitter Developer App Overview&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After clicking on the tab Keys and tokens you get your API-access data - you need all four, so save them!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zS5IlIWj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AuSCenmJUOvW9aV9l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zS5IlIWj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AuSCenmJUOvW9aV9l.jpg" alt="Twitter Developer API Keys"&gt;&lt;/a&gt;&lt;em&gt;Twitter Developer API Keys&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I use dotenv to prevent my API credentials from appearing publicly on Github. So I add four entries to my .env file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TWITTER\_CONSUMER\_KEY="YourAPIkey" TWITTER\_CONSUMER\_SECRET="YourAPIsecretkey" TWITTER\_ACCESS\_TOKEN\_KEY="YourAccesstoken" TWITTER\_ACCESS\_TOKEN\_SECRET="YourAccesstokensecret"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Create data object in Eleventy
&lt;/h3&gt;

&lt;p&gt;External data can be conveniently used in Eleventy. All you need to do is create a JavaScript file in the _data folder, which returns the desired data via return (see &lt;a href="https://www.11ty.io/docs/data-js/"&gt;Documentation&lt;/a&gt; for details). First create a file named tweets.js in the data folder.&lt;/p&gt;

&lt;p&gt;Then install the Twitter NodeJS package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install twitter --save
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;My tweets.js file starts with the required libraries:&lt;/p&gt;

&lt;p&gt;Next, the Twitter NodeJS package needs our API credentials:&lt;/p&gt;

&lt;p&gt;Now we can query different data from the API. We are interested in our own tweets, so we need our user ID (you can also use the account name, but the ID remains the same even if you change the name).&lt;/p&gt;

&lt;p&gt;We also add how many tweets we want to retrieve, I’ve chosen the 20 most recent tweets.&lt;/p&gt;

&lt;p&gt;If you like, you can add several other options here, a complete overview can be found in the &lt;a href="https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline"&gt;documentation on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The only thing missing is the retrieval of the data and its export:&lt;/p&gt;

&lt;p&gt;Altogether my tweets.js looks like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  11ty filter
&lt;/h3&gt;

&lt;p&gt;Eleventy offers the possibility to define your own filters. I currently use two basic filters for the display of tweets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter replies
&lt;/h3&gt;

&lt;p&gt;By default, the API sends each tweet to the timeline, including the tweets you posted in response to other tweets. There are two ways to filter the responses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Filter by API query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You add the value exclude_replies = true to your params variable. However, I decided not to do this because we cannot access the answers on any other page - they are not queried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Filter by 11ty-Filter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With a global filter you can dynamically decide in the template where you want to include answers. The trick: Each reply starts with an @. The corresponding filter belongs in your .eleventy.js file:&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter URL from tweet text
&lt;/h3&gt;

&lt;p&gt;By default, the API appends the source URL of the tweet to each tweet text. Each of these URLs starts with Twitter’s short URL service — and I take advantage of that in this filter by filtering every URL that starts with &lt;a href="https://t.co."&gt;https://t.co.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to display the date of your tweets, you won’t be able to avoid formatting. The Twitter API displays the date in the format Thu Apr 06 15:28:43 +0000 2017.&lt;/p&gt;

&lt;p&gt;For formatting I use &lt;a href="https://momentjs.com/"&gt;Moment.js&lt;/a&gt;. Moment.js allows you not only to change the format of the date, but also to influence the localization. Since I use the filter not only for tweets, but also for other data, I built it as flexible as possible, instead of setting it to the Twitter format.&lt;/p&gt;

&lt;p&gt;The filter can now be used as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ item.created\_at | date("ddd MMM D HH:mm:ss ZZ YYYY", "Do MMMM YYYY", language) }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Prepare template
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Displaying tweets
&lt;/h3&gt;

&lt;p&gt;After we have formed a data object from the Twitter API into Eleventy, any value can be used dynamically in the template. I use Nunjucks for this.&lt;/p&gt;

&lt;p&gt;First we set the tweetExcludeAnswers filter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% set twitter = tweets | tweetExcludeAnswers %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Afterwards we can loop through the data. In this case I also limit the display to the five most recent tweets by using .slice(0, 5).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{%- for item in twitter.slice(0, 5) -%} \&amp;lt;!-- Show tweets --\&amp;gt; {%- endfor -%}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Query for tweet type (retweet, quote, own tweet)
&lt;/h3&gt;

&lt;p&gt;Each tweet can be assigned to one of three categories: A retweet, a quoted tweet (i.e. a retweet with an attached comment) or an original tweet. Each tweet item therefore has two attributes, retweeted and is_quote_status, which is provided with true or false. If both values are false, the tweet is an original tweet.&lt;/p&gt;

&lt;p&gt;With this knowledge we can build the templates accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if item.retweeted %}{% endif %}

{% if item.is\_quote\_status %}{% endif %}

{% if not item.is\_quote\_status and not item.retweeted %}{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can find my complete code with all adjustments in my Github repository:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennishagemeier/d-hagemeier/blob/master/src/_data/tweets.js"&gt;Tweet.js file&lt;/a&gt; for creating the data object&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennishagemeier/d-hagemeier/blob/master/.eleventy.js"&gt;Eleventy.js file&lt;/a&gt; with the required filters&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennishagemeier/d-hagemeier/blob/master/src/_includes/components/tweets.njk"&gt;Nunjucks-Template&lt;/a&gt; for displaying the tweets&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Bonus: Automatic deploy on new tweet
&lt;/h3&gt;

&lt;p&gt;Since the tweets are only imported in the build process and embedded in the homepage HTML, the page has to be updated with a new tweet. If you use Netlify for hosting, you can save a lot of manual work with automatic deploys.&lt;/p&gt;

&lt;p&gt;The trick: Webhooks and the link to IFTTT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Build-Hook
&lt;/h3&gt;

&lt;p&gt;First navigate to Settings &amp;gt; Build &amp;amp; deploy &amp;gt; Continuous Deployment &amp;gt; Build hooks in your Netlify account. After clicking on "Add build hook" only the name is missing (in my case "New tweet") and the selection of the branch. Netlify then shows you a URL in the format &lt;a href="https://api.netlify.com/build%5C_hooks/BUILDID"&gt;https://api.netlify.com/build\_hooks/BUILDID&lt;/a&gt; - save this one!&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger rebuild in IFTTT
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt; you now create a new applet. Our trigger is the Twitter account: The task should always be executed as soon as a new tweet appears in your account.&lt;/p&gt;

&lt;p&gt;The action is then “Make a web request”. Enter the build hook URL just received from Netlify under “URL”, “Method” is “POST” and for “Content Type” we use “application/x-www-form-urlencoded”.&lt;/p&gt;

&lt;p&gt;This is what the finished task looks like for me:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_x4ru6E---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/564/0%2AIPQgmNH5XMEHbm_0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_x4ru6E---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/564/0%2AIPQgmNH5XMEHbm_0.jpg" alt="IFTTT settings for automatic deployment in Netlify"&gt;&lt;/a&gt;&lt;em&gt;IFTTT settings for automatic deployment in Netlify&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s it, have fun with your tweets!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.d-hagemeier.com/en/articles/embed-twitter/"&gt;&lt;em&gt;https://www.d-hagemeier.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on October 16, 2019.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>node</category>
      <category>11ty</category>
      <category>api</category>
    </item>
    <item>
      <title>From WordPress to JAMStack</title>
      <dc:creator>Dennis Hagemeier</dc:creator>
      <pubDate>Tue, 20 Aug 2019 17:30:00 +0000</pubDate>
      <link>https://dev.to/dennisview/from-wordpress-to-jamstack-1ob4</link>
      <guid>https://dev.to/dennisview/from-wordpress-to-jamstack-1ob4</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B0v6snJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A1-C77YdcsonHe7CJ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B0v6snJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A1-C77YdcsonHe7CJ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Away from WordPress, towards JAMStack with Eleventy and Netlify. Multilingualism, a domain move… August makes everything new.&lt;/p&gt;

&lt;p&gt;New design, new technology, faster loading performance — the new version of my private website is finally live 🎉&lt;/p&gt;

&lt;p&gt;A good occasion to show what has been going on in the backend, what I intend to do with this site in the future and to give you suggestions for your own blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multilingualism &amp;amp; domain transfer
&lt;/h3&gt;

&lt;p&gt;The first change: Instead of d-hagemeier.de my blog is now accessible via d-hagemeier.com. The main reason for this step is the future bilingualism of all content. Each article will be published in German and English in the future (how I have achieved this technically, will soon be published in a separate article).&lt;/p&gt;

&lt;p&gt;At the same time the content was old. 2016-old. Basically, an archive of my articles published on other websites. What was missing was the discipline to write more articles.&lt;/p&gt;

&lt;p&gt;Marketing, web development and design will be the future focus, my personal goal is to write at least one article per month. Maybe, there will be one or two articles beyond these topics — who knows? 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s a match: Eleventy &amp;amp; Netlify
&lt;/h3&gt;

&lt;p&gt;Technically, the biggest step is the change to &lt;a href="https://www.11ty.io/"&gt;Eleventy&lt;/a&gt; and &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. For years I have built up all my professional and private projects on WordPress, my first choice for a CMS. But with the release of version 5.0, WordPress seemed bloated to me. It was time for something new.&lt;/p&gt;

&lt;p&gt;I had heard about the JAMStack ( &lt;strong&gt;J&lt;/strong&gt; avaScript, &lt;strong&gt;A&lt;/strong&gt; PIs and &lt;strong&gt;M&lt;/strong&gt; arkup), but it was quite difficult to get started. The basic idea is a new approach for high-performance, easy-to-manage websites. Instead of using PHP and databases like WordPress, the JAMStack generates HTML files that can be delivered “serverless”.&lt;/p&gt;

&lt;p&gt;Sounds static? Apart from the name of the generators (Static Site Generators, or SSG for short), it isn’t static at all. To make programming as flexible as possible, SSGs rely primarily on template languages such as Liquid or Nunjucks. Variables, filters or loops are resolved and converted during build process.&lt;/p&gt;

&lt;p&gt;Other tasks can be solved via JavaScript and the connection of APIs. This makes it possible to solve even complex ordering processes such as those of an online shop without relying on server-based programming languages.&lt;/p&gt;

&lt;p&gt;Great theories, yet the introduction was difficult for me. I was used to PHP, had a hard time fiddling with the given structure of the leading SSGs, like &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This changed when I discovered &lt;a href="https://twitter.com/zachleat"&gt;Zach Leathermans&lt;/a&gt; Eleventy. Based on NodeJS, you get maximum flexibility in the structure, almost every template language imaginable, a detailed documentation with numerous tutorials and starter projects… the start couldn’t have been better.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Eleventy work with my website?
&lt;/h3&gt;

&lt;p&gt;All my articles are written as Markdown files. Additional information such as title, publication date or SEO information ends up in the header of the article. Simplified, an article looks like this:&lt;/p&gt;

&lt;p&gt;Eleventy does not specify in which directory these Markdown files are located. At the same time I use Nunjucks to create the templates. If you open a Nunjucks file for the first time, the code looks like HTML. Finally there is nothing magical about it — Nunjucks is just an extension for functions and variables.&lt;/p&gt;

&lt;p&gt;The basic layout for each content type is very simple:&lt;/p&gt;

&lt;p&gt;With include I load additional components, in this case the head and footer area. The head contains nothing else than the doctype, meta tags or the link of the stylesheet (similar to header.php in WordPress). The cool thing about Nunjucks: By using variables from the head of the markdown file, all HTML generated afterwards can be adapted dynamically. For example, the  tag looks like this:&lt;/p&gt;

&lt;p&gt;For this example, this would result in nothing more than:&lt;/p&gt;

&lt;p&gt;By the way, I took only one article from the old version of my website and copied it manually. If I already had a larger amount of articles in WordPress, I would have used the &lt;a href="https://www.npmjs.com/package/wordpress-export-to-markdown"&gt;Wordpress Export to Markdown&lt;/a&gt; to generate markdown files from the WP export file.&lt;/p&gt;

&lt;p&gt;Another change of this new website version: All source code is publicly available on Github. So if you want to take a closer look at the structure, feel free to check out the &lt;a href="https://github.com/dennishagemeier/d-hagemeier"&gt;Repository&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Although being called “serverless”, you still need a server. So I needed a hoster.&lt;/p&gt;

&lt;p&gt;So far, my private blog was accessible on a webspace of &lt;a href="https://uberspace.de/"&gt;uberspace&lt;/a&gt;. For a “classic” website, I would probably never have changed — the support is out-of-this-world, the performance better than most supercars and the whole business model is based on “Pay what you want”.&lt;/p&gt;

&lt;p&gt;But then, along came &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. And so my choice was made.&lt;/p&gt;

&lt;p&gt;Anyone who asks “why” should try Netlify. Within three minutes my website was online — Netlify only needs to specify a repository, then downloads all required packages, executes the defined build process and provides the live directory directly under a .netlify.com subdomain.&lt;/p&gt;

&lt;p&gt;Additional gimmicks simplify a lot, like optimizing CSS or image files, optimizing URLs or creating dynamic redirects by language.&lt;/p&gt;

&lt;p&gt;The page will be rebuilt as soon as something changes in the Github directory. Or you can use webhooks and trigger the deploy manually (for my tweets on the home page for example).&lt;/p&gt;

&lt;h3&gt;
  
  
  ToDos
&lt;/h3&gt;

&lt;p&gt;As always, there is still a lot on my ToDo list.&lt;/p&gt;

&lt;p&gt;Currently the website does not contain any category pages. Thanks to tags in Eleventy, custom archive pages are very easy to build (in my case all articles are already divided into matching collections).&lt;/p&gt;

&lt;p&gt;I also want to experiment with webmentions. This is a protocol from the IndieWeb, with which information like comments, likes or reposts can be transferred in a standardized way. Thanks to tools like &lt;a href="https://brid.gy/"&gt;Bridgy&lt;/a&gt;, you can even import data from Twitter or Instagram.&lt;/p&gt;

&lt;p&gt;My plan: Implement &lt;a href="https://mxb.dev/blog/using-webmentions-on-static-sites/"&gt;Max Böcks&lt;/a&gt; great instructions and show all comments to articles like this one below the article.&lt;/p&gt;

&lt;p&gt;Also, I’m working on automatically generate OG-images, the SVG-integration is not optimal yet… you notice, I still have some things to do 😄&lt;/p&gt;

&lt;p&gt;Please give me your feedback or write a short message, if you should notice any errors. I’m not finished with this blog yet 😛&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.d-hagemeier.com/en/articles/wordpress-to-jamstack/"&gt;&lt;em&gt;https://www.d-hagemeier.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on August 20, 2019.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>jamstack</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
