<?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: Florian Kapfenberger</title>
    <description>The latest articles on DEV Community by Florian Kapfenberger (@phiilu).</description>
    <link>https://dev.to/phiilu</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%2F7997%2Ff750bc78-6f51-4bfb-a008-d372a8a604ff.jpeg</url>
      <title>DEV Community: Florian Kapfenberger</title>
      <link>https://dev.to/phiilu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/phiilu"/>
    <language>en</language>
    <item>
      <title>Password protect your (Vercel) site with Cloudflare Workers</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Sat, 03 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/password-protect-your-vercel-site-with-cloudflare-workers-4m99</link>
      <guid>https://dev.to/phiilu/password-protect-your-vercel-site-with-cloudflare-workers-4m99</guid>
      <description>&lt;p&gt;I am working on a new project where I already setup the deployment pipeline. The app has a backend and a frontend. The frontend is built with Next.js and therefore I deployed it to Vercel. Because the state of the current project is not publishable I want to protect the access with a password. Vercel offers this option, but you need to have the &lt;a href="https://vercel.com/pricing"&gt;Pro plan&lt;/a&gt; that costs 20$ per month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qlfFWTnZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/3fYxWe5RzX2KHSL4vo7Lqn/c09901d7aef8eb145e5237b6af5b4565/vercel-pricing.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qlfFWTnZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/3fYxWe5RzX2KHSL4vo7Lqn/c09901d7aef8eb145e5237b6af5b4565/vercel-pricing.PNG" alt="Vercel Pricing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alright that would not be too bad, but wait why is there a small info icon next to password protection 🤔&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GZHO9Ptp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/5D0fG4zITfhW8ghDx0ZRfF/114c0e1b204bf3c162493029dae35688/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GZHO9Ptp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/5D0fG4zITfhW8ghDx0ZRfF/114c0e1b204bf3c162493029dae35688/image.png" alt="Vercel Password Protection Pricing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is way out of my budget and I don't understand why Vercel is selling this feature with such a high price tag. &lt;a href="https://www.netlify.com/pricing/"&gt;Netlify&lt;/a&gt; for example offers password protection too, but they have it included in the Pro plan for 19$ / month and no additional costs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x_X9qAMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/4bJ14YC17Dn3wH4zyJfeH5/d09699a0085da767fb0120b5879677a3/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x_X9qAMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/4bJ14YC17Dn3wH4zyJfeH5/d09699a0085da767fb0120b5879677a3/image.png" alt="Netlify Pricing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I thought maybe I can add password protection with something else. And yes there is a solution: &lt;a href="https://workers.cloudflare.com/"&gt;Cloudflare Workers&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;To learn about Cloudflare Workers (CW) I would suggest you &lt;a href="https://developers.cloudflare.com/workers/learning/how-workers-works"&gt;look at the docs&lt;/a&gt;. Basically, they are similar to serverless functions, but without the downsides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's protect our site with Cloudflare Workers
&lt;/h2&gt;

&lt;p&gt;First of all your domain needs to be managed by Cloudflare to be used together with CW. If you have this working it is just a matter of minutes to have your site protected.&lt;/p&gt;

&lt;p&gt;To create a CW you need to go to your Dashboard and select Workers on the right side of the navigation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MUCHT9PG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/3OyQLbr0yWASuF6FpBg2Vk/565b2c0e431a05d1d88fb87ec40cf0bc/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MUCHT9PG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/3OyQLbr0yWASuF6FpBg2Vk/565b2c0e431a05d1d88fb87ec40cf0bc/image.png" alt="Cloudflare Workers link on the dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you click &lt;strong&gt;Create a Worker&lt;/strong&gt; and you should be shown the editor where we can write our code.&lt;/p&gt;

&lt;p&gt;Of course, I was not the first one that had the idea to use CW to protect their site with it. After a quick Google search, I found &lt;a href="https://github.com/dommmel/cloudflare-workers-basic-auth"&gt;this Repository&lt;/a&gt; on GitHub by &lt;a href="https://github.com/dommmel"&gt;dommmel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to his efforts in creating this worker it was as easy as copying the contents of the &lt;a href="https://github.com/dommmel/cloudflare-workers-basic-auth/blob/master/index.js"&gt;index.js&lt;/a&gt; file and past it into the editor.&lt;/p&gt;

&lt;p&gt;The only things you need to change now are the &lt;code&gt;NAME&lt;/code&gt;, and &lt;code&gt;PASS&lt;/code&gt; at the top. Between lines 76 and 77 you paste the following code:&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="nx"&gt;request&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;startsWith&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="nx"&gt;protocol&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="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.well-known/acme-challenge`&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;This will allow Vercel to still be able to generate Let's Encrypt certificates.&lt;/p&gt;

&lt;p&gt;After that your code should look like this with your custom username and password:&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;NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CUSTOM_USER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PASS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUPER_SECRET_PASSWORD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * RegExp for basic auth credentials
 *
 * credentials = auth-scheme 1*SP token68
 * auth-scheme = "Basic" ; case insensitive
 * token68     = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CREDENTIALS_REGEXP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^ *&lt;/span&gt;&lt;span class="se"&gt;(?:[&lt;/span&gt;&lt;span class="sr"&gt;Bb&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;Aa&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;Ss&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;Ii&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;Cc&lt;/span&gt;&lt;span class="se"&gt;])&lt;/span&gt;&lt;span class="sr"&gt; +&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9._~+&lt;/span&gt;&lt;span class="se"&gt;/&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+=*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; *$/&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * RegExp for basic auth user/pass
 *
 * user-pass   = userid ":" password
 * userid      = *&amp;lt;TEXT excluding ":"&amp;gt;
 * password    = *TEXT
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;USER_PASS_REGEXP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Object to represent user credentials.
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&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;name&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Parse basic auth to object.
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseAuthHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// parse header&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CREDENTIALS_REGEXP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&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;match&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="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// decode user pass&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userPass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;USER_PASS_REGEXP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;userPass&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="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// return credentials object&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;Credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userPass&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;userPass&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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;unauthorizedResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WWW-Authenticate&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;Basic realm="User Visible Realm"&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="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Handle request
 */&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&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;protocol&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="nx"&gt;request&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;startsWith&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="nx"&gt;protocol&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="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.well-known/acme-challenge`&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&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;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parseAuthHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&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;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="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&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;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;PASS&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;unauthorizedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&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="k"&gt;else&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;addEventListener&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="nx"&gt;event&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&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;Now that we have our worker finished we need can change the name at the top left and with &lt;strong&gt;Save and Deploy&lt;/strong&gt; we can (duh..) deploy it!&lt;/p&gt;

&lt;p&gt;The worker is now ready to be used and we can assign it to a custom domain. On Cloudflare open up your domain and go to the &lt;strong&gt;Workers&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rw2Wv35L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/41wQSOoekb4p3rvE3oiCKw/8d172776dee9b93f59f1b5b00493a9dd/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rw2Wv35L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/41wQSOoekb4p3rvE3oiCKw/8d172776dee9b93f59f1b5b00493a9dd/image.png" alt="Cloudflare Workers tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Add Route&lt;/strong&gt; and type the text from the placeholder into the input field. If your domain is &lt;strong&gt;example.com&lt;/strong&gt; add &lt;strong&gt;*example.com/*&lt;/strong&gt;. In the select box, select the newly created CF. This instructs Cloudflare to use this worker whenever the URL matches the route we defined.&lt;/p&gt;

&lt;p&gt;On the DNS tab make sure the domain is proxied through Cloudflare (orange cloud), otherwise, the CW would not work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it work with Vercel
&lt;/h2&gt;

&lt;p&gt;Now that the Cloudflare Worker is set up and running, we still need to change a few settings in Cloudflare to make it work nicely with Vercel.&lt;/p&gt;

&lt;p&gt;The first thing we check is on the SSL/TLS tab in Cloudflare. Make sure you have it set to Full (strict) otherwise you will get a Too many redirects error and can't access your site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rDwcbOjZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/1iGsTxhWJepVcnbfZgHBTb/8d1897b3f340442d14aa65b79a20f326/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rDwcbOjZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/1iGsTxhWJepVcnbfZgHBTb/8d1897b3f340442d14aa65b79a20f326/image.png" alt="Cloudflare SSL/TLS settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we need to tell Cloudflare that the route &lt;code&gt;/.well-known/*&lt;/code&gt; can be accessed with HTTP instead of HTTPS. This is used by Vercel to create a Let's Encrypt certificate.&lt;/p&gt;

&lt;p&gt;To do this we need to go to the Page Rules tab on Cloudflare and create a new Page Rule. Enter in the first input field &lt;code&gt;/.well-known/*&lt;/code&gt; and select in the select field &lt;strong&gt;SSL&lt;/strong&gt; and &lt;strong&gt;Off&lt;/strong&gt;. With &lt;strong&gt;Save and Deploy&lt;/strong&gt; your rule will be applied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MgVGfEtJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/3JKJsP2UVwphJFdnemByuO/b21aaa313fce97832458e74adbf93488/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MgVGfEtJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/3JKJsP2UVwphJFdnemByuO/b21aaa313fce97832458e74adbf93488/image.png" alt="Add Clouflare Page Rule to disable HTTPS on /.well-known/*"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are finally done 🥳&lt;/p&gt;

&lt;p&gt;Now your page should be protected using Basic HTTP authentication with the username and password you provided in the Cloudflare Worker.&lt;/p&gt;

&lt;p&gt;I have set up a simple demo site where I protected an HTML site hosted on Vercel using the Cloudflare Worker we just created.&lt;/p&gt;

&lt;p&gt;The site is accessible at &lt;a href="https://protected-site-example.phiilu.com/"&gt;https://protected-site-example.phiilu.com/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Username:&lt;/strong&gt; phiilu&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password:&lt;/strong&gt; shiba-inus-are-awesome!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thanks to Cloudflare Workers we can simply add password protection to our sites!&lt;/p&gt;

&lt;p&gt;In my opinion, such a feature should not cost 150$ a month and I hope that Vercel will rethink this choice and adds this as an included feature in the Pro plan.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>serverless</category>
      <category>cloudflareworkers</category>
    </item>
    <item>
      <title>Allow your mobile visitors to easier share your articles with the Web Share API</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Sun, 14 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/allow-your-mobile-visitors-to-easier-share-your-articles-with-the-web-share-api-4cbi</link>
      <guid>https://dev.to/phiilu/allow-your-mobile-visitors-to-easier-share-your-articles-with-the-web-share-api-4cbi</guid>
      <description>&lt;p&gt;Sharing websites is a fundamental process of the web and providing a way to make it as easy as possible can allow users to easily share the work you created.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.w3.org/TR/web-share/"&gt;Web Share API&lt;/a&gt; allows the user to use the native share mechanism of the operating system. You probably already have seen this when you want to share an image or video using a native app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iyCXECYC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/4aZeQQwhacNZSAuS8J3ka3/90f10885ec016c11639a17d39fcf2ec2/IMG_174065CE3004-1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iyCXECYC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/4aZeQQwhacNZSAuS8J3ka3/90f10885ec016c11639a17d39fcf2ec2/IMG_174065CE3004-1.jpeg" alt="iOS Share Sheet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because this is using the native sharing mechanism of the OS the Web Share API is not available in all browsers and OS. At the time of writing it is only available in Chrome (only Windows and Chrome OS), Edge, Safari, iOS Safari, Chrome for Android, Firefox for Android, and Samsung Internet. You can find an up-to-date list on &lt;a href="https://caniuse.com/web-share"&gt;caniuse.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H_oc3xom--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/2o3mOIgD492EJ3RxpKbjTr/397db663f087f383da61019734eb01d5/web-share-1613305231346.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H_oc3xom--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/2o3mOIgD492EJ3RxpKbjTr/397db663f087f383da61019734eb01d5/web-share-1613305231346.webp" alt="Can I Use - Web Share API (14. February 2021)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Web Share API if it is available
&lt;/h2&gt;

&lt;p&gt;Browsers that support the Web Share API will expose the &lt;code&gt;share&lt;/code&gt; method on the &lt;code&gt;navigator&lt;/code&gt; object. There is also a &lt;code&gt;canShare&lt;/code&gt; method, but this method is not available in all browsers. So the best way to check if it is available would be to check if the &lt;code&gt;share&lt;/code&gt; method exists.&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Web Share API available&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="c1"&gt;// Use a fallback method&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Share URLs with the Web Share API
&lt;/h2&gt;

&lt;p&gt;Using the Web Share API is very easy. In its basic version, it is just one function that you call and that's it.&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow your mobile visitors to easier share your articles with the Web Share API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://phiilu.com/share-articles-on-social-media-with-the-web-share-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow your mobile visitors to easier share your articles with the Web Share API by Florian Kapfenberger (@phiilu)&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sharePromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;share&lt;/code&gt; method takes an object as its first argument. Here you can define what should be prefilled by the share method that will be used later on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt;: The title that should be prefilled (if supported)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;url&lt;/code&gt;: The website/URL you want to share&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text&lt;/code&gt;: A custom text which should be prefilled before sharing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Share files with the Web Share API
&lt;/h2&gt;

&lt;p&gt;By adding a &lt;code&gt;files&lt;/code&gt; array to the &lt;code&gt;share&lt;/code&gt; function you can allow the user to share images too.&lt;/p&gt;

&lt;p&gt;Unfortunately, I can't get it working with the devices I have. I tested it with an iPhone 12 (iOS 14) and on macOS.&lt;/p&gt;

&lt;p&gt;Here is a CodeSandbox if you want to test it on your device. If the code is not quite right, maybe fork it and write me on &lt;a href="https://twitter.com/phiilu"&gt;Twitter&lt;/a&gt; and I will update it!&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/web-share-api-sharing-files-8wlrc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How I added the Web Share API to my blog
&lt;/h2&gt;

&lt;p&gt;If you are reading this on &lt;a href="https://phiilu.com/"&gt;my blog&lt;/a&gt; and you are using a supported device you can test it out right now!&lt;/p&gt;

&lt;p&gt;If supported, you should see a button &lt;strong&gt;Share Anywhere&lt;/strong&gt;. Try it out :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--04h75iMV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/2cER3ladvU5pzLhRnOIcxv/6c0b892ed81d4ccebe0ea1e01d795c4b/RPReplay_Final1613311858.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--04h75iMV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://images.ctfassets.net/hb3id6ag4raq/2cER3ladvU5pzLhRnOIcxv/6c0b892ed81d4ccebe0ea1e01d795c4b/RPReplay_Final1613311858.gif" alt="Web Share API - iOS Share"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In React I created a new &lt;code&gt;Share&lt;/code&gt; component. If the &lt;code&gt;share&lt;/code&gt; method is available I will show the &lt;strong&gt;Share Anywhere&lt;/strong&gt; button additional to the "Fallback" share buttons.&lt;/p&gt;

&lt;p&gt;You can find the up-to-date file on my &lt;a href="https://github.com/phiilu/phiilu.com/blob/main/src/components/Share/Share.js"&gt;GitHub&lt;/a&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-hot-toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@components/Button/Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&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;onClick&lt;/span&gt; &lt;span class="p"&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;isSareApiAvailable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsSareApiAvailable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&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="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useEffect&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="nx"&gt;setIsSareApiAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleSocialShare&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&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="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; by Florian Kapfenberger (@phiilu)`&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;toast&lt;/span&gt;&lt;span class="p"&gt;.&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;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share the post with the world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Select how you want to share the post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;dismiss&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="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shared successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thank you for sharing my post!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;dismiss&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;So close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Oh okay.. Maybe next time :)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dismiss&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="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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// do nothing&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="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-y-2 sm:items-start sm:space-x-2 sm:space-y-0 xl:space-y-2 sm:flex xl:space-x-0 xl:block&lt;/span&gt;&lt;span class="dl"&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;isSareApiAvailable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;
            &lt;span class="nx"&gt;tracking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
              &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share Anywhere clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share Anywhere clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}}&lt;/span&gt;
            &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secondary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSocialShare&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;Share&lt;/span&gt; &lt;span class="nx"&gt;Anywhere&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;
          &lt;span class="nx"&gt;tracking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share on Twitter clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share on Twitter clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;`https://twitter.com/share?text=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; via @phiilu&amp;amp;url=&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="s2"&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;twitter-share&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;width=550,height=235&lt;/span&gt;&lt;span class="dl"&gt;'&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;Share&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;Twitter&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;
          &lt;span class="nx"&gt;tracking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share on Hacker News clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Share on Hacker News clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hackernews&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;`https://news.ycombinator.com/submitlink?u=&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="s2"&gt;&amp;amp;t=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hn-share&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;width=550,height=350&lt;/span&gt;&lt;span class="dl"&gt;'&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;Share&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;Hacker&lt;/span&gt; &lt;span class="nx"&gt;News&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Share&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With just a few lines of code, you can improve the sharing process for your mobile users, and for unsupported browsers, you can provide a fallback method. Hopefully, this will not be needed anymore in the future.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MDN: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share"&gt;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;W3C: &lt;a href="https://www.w3.org/TR/web-share/"&gt;https://www.w3.org/TR/web-share/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>react</category>
    </item>
    <item>
      <title>Accessing the Clipboard in Javascript using the Clipboard API</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Sun, 07 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/accessing-the-clipboard-in-javascript-using-the-clipboard-api-5h2j</link>
      <guid>https://dev.to/phiilu/accessing-the-clipboard-in-javascript-using-the-clipboard-api-5h2j</guid>
      <description>&lt;p&gt;Welcome to my new series where I will write about built-in Browser-APIs and demonstrate how you can use them today. Most of the APIs I will write about will be highly experimental and still will have the "Draft" status. So don't expect a wide browser adoption and only try this with modern browsers. You can always check &lt;a href="https://caniuse.com/"&gt;caniuse.com&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API"&gt;MDN&lt;/a&gt; for up-to-date information about the compatibility in the browsers.&lt;/p&gt;

&lt;p&gt;The first API I want to write about is the Clipboard API. With the Clipboard API, you can read from and write to the Clipboard of the user. It is not something new as you already can do this with the &lt;code&gt;document.execCommand()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;code&gt;execCommand()&lt;/code&gt; is a synchronous operation and will block the page while calling it. Also, a browser might implement the &lt;code&gt;document.execCommand('copy')&lt;/code&gt; function differently than another one. This could lead to inconsistent results. You can read more about the old way &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Clipboard API tries to solve this by being asynchronous and offer a more consistent API across browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions &amp;amp; Security for accessing the Clipboard
&lt;/h2&gt;

&lt;p&gt;With the new Clipboard API, you will need the &lt;code&gt;clipboard-read&lt;/code&gt; permission to read the user clipboard content. When you want to write to the clipboard you will need to have the &lt;code&gt;clipboard-write&lt;/code&gt; permission. Although you will need this permission, the browser grants you the permission automatically when the page is in the current tab.  &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API"&gt;Permissions API&lt;/a&gt; is not supported in all browsers yet. Safari for example did not implement it, however, you can access the Clipboard API in Safari without requesting permissions. Firefox on the other hand supports the Permissions API but did not implement the  &lt;code&gt;clipboard-read&lt;/code&gt; and &lt;code&gt;clipboard-write&lt;/code&gt; permissions yet. You already see that there might be issues if you start using it today. &lt;/p&gt;

&lt;p&gt;Also, you will need to have a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"&gt;secure context&lt;/a&gt; while accessing the Clipboard API. This usually means you can only access this API when you are on &lt;code&gt;localhost&lt;/code&gt; or a webpage with a valid SSL/TLS certificate.&lt;/p&gt;

&lt;p&gt;You can access the Clipboard API on the &lt;code&gt;navigator&lt;/code&gt; object. If &lt;code&gt;navigator.clipboard&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;, then the Clipboard API won't be available on your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Listening for Clipboard Events
&lt;/h2&gt;

&lt;p&gt;If you want to act on if the user is copying, pasting, or cutting you can very easily get those events by attaching an event listener to the document.  The names for these events are &lt;code&gt;copy&lt;/code&gt;, &lt;code&gt;paste&lt;/code&gt;, and &lt;code&gt;cut&lt;/code&gt; events.&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;function&lt;/span&gt; &lt;span class="nx"&gt;handlePasteEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;handleClipboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paste&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handlePasteEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event that gets passed to the handler function will implement the &lt;code&gt;ClipboardEvent&lt;/code&gt; interface. This interface will allow you to override the content of what is being passed to or from the Clipboard. Very handy if you want to format or remove the formatting of text that is being copied.&lt;/p&gt;

&lt;p&gt;Reading from the Clipboard&lt;br&gt;
For browsers that implement the Permissions API, you should first check if you have permission to read from the Clipboard. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I tried it out without querying for permission and it was working fine. This could obviously change in the future.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard-read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;granted&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="c1"&gt;// you can read from the clipboard&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;h3&gt;
  
  
  Text
&lt;/h3&gt;

&lt;p&gt;If you only want to read text you can use the convenience method &lt;code&gt;readText()&lt;/code&gt;. This method will return a Promise with the contents of the clipboard.&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;text&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;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Images
&lt;/h3&gt;

&lt;p&gt;If you want to read images from the clipboard you will have to use the &lt;code&gt;read()&lt;/code&gt; method. This method will return a Promise containing an array of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem"&gt;ClipboardItem&lt;/a&gt; objects.&lt;/p&gt;

&lt;p&gt;Here is an example of how you can read an image from the clipboard:&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&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;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clipboardItems&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="k"&gt;for&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;clipboardItem&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;clipboardItems&lt;/span&gt;&lt;span class="p"&gt;)&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;clipboardItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&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;blob&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;clipboardItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&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;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageUrl&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="nx"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// blob:http://localhost:3000/e53d5d55-c674-454b-9587-2501a4396eed&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="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;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;Basically what we are doing here is to iterate over each item and check the MIME type and if it is a PNG image we will create an object URL that can be passed to the &lt;code&gt;src&lt;/code&gt; attribute of an image to display it. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;URL.createObjectURL()&lt;/strong&gt; static method creates a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMString"&gt;DOMString&lt;/a&gt; containing a URL representing the object given in the parameter. The URL lifetime is tied to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document"&gt;document&lt;/a&gt; in the window on which it was created. The new object URL represents the specified &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File"&gt;File&lt;/a&gt; object or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob"&gt;Blob&lt;/a&gt; object. - &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL"&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Writing to the Clipboard
&lt;/h2&gt;

&lt;p&gt;As I already said you won't need to ask for the &lt;code&gt;clipboard-write&lt;/code&gt; permissions because the browser will grant it automatically if the page is in the active tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text
&lt;/h3&gt;

&lt;p&gt;If you just want to write text to the clipboard you can use the convenience &lt;code&gt;writeText()&lt;/code&gt; method. Just pass the text you want to put on the clipboard as the first argument and you are done!.&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="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Images
&lt;/h3&gt;

&lt;p&gt;If you want to write images to the clipboard you will have to use the &lt;code&gt;write()&lt;/code&gt; method. This method takes an array of ClipboardItem objects as the first argument.&lt;/p&gt;

&lt;p&gt;Here is an example of how you can read an image from the clipboard:&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;result&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://images.dog.ceo/breeds/pembroke/n02113023_15926.jpg&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="nx"&gt;blob&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blob&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;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ClipboardItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blob&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;p&gt;Basically will have to create a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob"&gt;Blob&lt;/a&gt; out of that image. The easiest way I know how to achieve this is to call the URL of the image with &lt;code&gt;fetch&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Using the API with React
&lt;/h3&gt;

&lt;p&gt;I am mostly developing with React nowadays, therefore I want to show how I used it with it. There is not much different than using it without React, because we won't need access to any DOM elements and can do everything inside an event handler or &lt;code&gt;useEffect&lt;/code&gt; hook. &lt;/p&gt;

&lt;p&gt;I created an MVP of a Clipboard Manager without persistence, but it demonstrates most of the Clipboard API features very well.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DEMO: &lt;a href="https://clipboard-api.phiilu.com/"&gt;https://clipboard-api.phiilu.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/phiilu/clipboard-browser-api-demo-nextjs"&gt;https://github.com/phiilu/clipboard-browser-api-demo-nextjs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is using Next.js and Tailwind CSS. I kept everything inside the &lt;code&gt;index.js&lt;/code&gt; file. If you have any questions don't hesitate to ask me on &lt;a href="https://twitter.com/phiilu"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;At the time of writing the support for the Clipboard API is still limited and I think I won't be using it in production right now. &lt;/p&gt;

&lt;p&gt;I think it is important that the web will get more consistent APIs that are supported in most browsers and do the hard work for us. Thinking about how to copy a text to the clipboard should not be complicated. &lt;/p&gt;

&lt;p&gt;The Clipboard API will finally deliver a consistent API without using hacky methods. If you want to get more examples of how this API will help you, check out the &lt;a href="https://w3c.github.io/clipboard-apis/#Cases"&gt;W3C Use Cases&lt;/a&gt; section. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MDN: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard"&gt;https://developer.mozilla.org/en-US/docs/Web/API/Clipboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;web.dev: &lt;a href="https://web.dev/async-clipboard/"&gt;https://web.dev/async-clipboard/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;W3C: &lt;a href="https://w3c.github.io/clipboard-apis/"&gt;https://w3c.github.io/clipboard-apis/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>react</category>
    </item>
    <item>
      <title>Use your Raspberry Pi as a Docker server with docker-machine</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Thu, 07 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/use-your-raspberry-pi-as-a-docker-server-with-docker-machine-54ao</link>
      <guid>https://dev.to/phiilu/use-your-raspberry-pi-as-a-docker-server-with-docker-machine-54ao</guid>
      <description>&lt;p&gt;If you have a slow computer or you somehow can't install Docker using the official &lt;a href="https://www.docker.com/products/docker-desktop"&gt;Docker Desktop&lt;/a&gt; installer, you might want to use your Raspberry Pi (or any other server you have) to be your dedicated Docker server. &lt;/p&gt;

&lt;p&gt;Recently I had the problem that I could not install Docker Desktop in my VM that was running on my &lt;a href="https://www.unraid.net/"&gt;Unraid&lt;/a&gt; server. The problem is that on Ryzen CPUs nested virtualization is not (yet) supported on the OS that I used. &lt;a href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/nested-virtualization"&gt;Nested virtualization&lt;/a&gt; means that you can run a VM inside a VM.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/7u9jFuc5zgWhOX6aYeyc83/e7702108d2387b17a0587f12cb07ffce/yo-dawg-i-heard-you-like-virtual-machines-so-i-put-a-virtual-machine-inside-a-virtual-machine.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/7u9jFuc5zgWhOX6aYeyc83/e7702108d2387b17a0587f12cb07ffce/yo-dawg-i-heard-you-like-virtual-machines-so-i-put-a-virtual-machine-inside-a-virtual-machine.jpg" alt="Yo Dawg Virtual Machine Meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I could not use the official Docker for Mac installer to install Docker, I had to find another way. &lt;/p&gt;

&lt;p&gt;The answer to this was &lt;code&gt;docker-machine&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is docker-machine?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docker-machine&lt;/code&gt; is a CLI program from Docker that allows you to use &lt;code&gt;docker&lt;/code&gt; as you would normally, but execute the commands on a server instead. This is useful if you have multiple servers running docker and you can control them all by just changing the docker environment with one command &lt;code&gt;docker-machine env YOUR_ENV&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you don't have &lt;code&gt;docker-machine&lt;/code&gt; installed, try to install it with your OSs package manager or download it from &lt;a href="https://github.com/docker/machine/releases"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;On macOS you can install it like this from brew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;docker-machine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if you can't install Docker Desktop, you should still be able to download the &lt;code&gt;docker&lt;/code&gt; CLI, you will need it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing our server
&lt;/h2&gt;

&lt;p&gt;Before we use &lt;code&gt;docker-machine&lt;/code&gt; we need to make some configurations on the server first. &lt;/p&gt;

&lt;p&gt;I would recommend creating a new user that will be used to login with &lt;code&gt;docker-machine&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser docker-machine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to allow the new user to execute commands with &lt;code&gt;sudo&lt;/code&gt;, but with the addition that the user can use it without entering its password. This might be a security risk, so make sure the password of the &lt;code&gt;docker-machine&lt;/code&gt; user is strong and your server is properly hardened. &lt;/p&gt;

&lt;p&gt;Edit the &lt;code&gt;sudoers&lt;/code&gt; file by writing &lt;code&gt;visudo&lt;/code&gt; into your terminal and append a new line after the entry for &lt;code&gt;root&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-machine &lt;span class="nv"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;ALL&lt;span class="o"&gt;)&lt;/span&gt; NOPASSWD: ALL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and quit the editor. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;docker-machine&lt;/code&gt; generic driver does not recognize the &lt;code&gt;raspbian&lt;/code&gt; OS, so we need to trick it, by temporarily changing the &lt;code&gt;ID&lt;/code&gt; field in the &lt;code&gt;/etc/os-release&lt;/code&gt; to &lt;code&gt;debian&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#/etc/os-release&lt;/span&gt;
&lt;span class="nv"&gt;PRETTY_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Raspbian GNU/Linux 10 (buster)"&lt;/span&gt;
&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Raspbian GNU/Linux"&lt;/span&gt;
&lt;span class="nv"&gt;VERSION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"10"&lt;/span&gt;
&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"10 (buster)"&lt;/span&gt;
&lt;span class="nv"&gt;VERSION_CODENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;buster
&lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian
&lt;span class="nv"&gt;ID_LIKE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian
&lt;span class="nv"&gt;HOME_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.raspbian.org/"&lt;/span&gt;
&lt;span class="nv"&gt;SUPPORT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.raspbian.org/RaspbianForums"&lt;/span&gt;
&lt;span class="nv"&gt;BUG_REPORT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.raspbian.org/RaspbianBugs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you will need to set up public-key authentication on your server with the &lt;code&gt;docker-machine&lt;/code&gt; user. I have documented the process on how you can do this in &lt;a href="https://phiilu.com/use-visual-studio-code-remote-to-edit-files-on-servers"&gt;another article&lt;/a&gt;. Scroll down to the &lt;strong&gt;"Skip passwords with public key authentication"&lt;/strong&gt; section and follow the steps to set it up.&lt;/p&gt;

&lt;p&gt;Now your server should be ready to install docker using &lt;code&gt;docker-machine&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Install docker on your server with docker-machine
&lt;/h2&gt;

&lt;p&gt;Now that you have the docker-machine CLI installed on your machine, we can use it to install docker on the server using just one command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-machine create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; generic &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--generic-ip-address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.13 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--generic-ssh-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker-machine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--generic-ssh-key&lt;/span&gt; ~/.ssh/id_rsa &lt;span class="se"&gt;\&lt;/span&gt;
  rpi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running pre-create checks...
Creating machine...
(rpi) Importing SSH key...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with debian...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env rpi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it you should now have docker installed on your server! &lt;/p&gt;

&lt;h2&gt;
  
  
  Change your docker environment with docker-machine
&lt;/h2&gt;

&lt;p&gt;Now if you want to execute docker commands on the server you just need to change your docker environment with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker-machine &lt;span class="nb"&gt;env &lt;/span&gt;rpi&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you always want to use the server environment, put the line into your &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt; file. &lt;/p&gt;

&lt;h2&gt;
  
  
  Start using Docker
&lt;/h2&gt;

&lt;p&gt;After changing the environment with &lt;code&gt;docker-machine&lt;/code&gt; you can use &lt;code&gt;docker&lt;/code&gt; the same way that you would do normally!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Useful docker-machine commands
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker-machine ls&lt;/code&gt; - list all environments&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-machine rm ENV&lt;/code&gt; - deletes an existing environment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-machine ip ENV&lt;/code&gt; - get the IP address of the server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-machine ssh ENV&lt;/code&gt; - open an SSH connection to the server &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docker-machine&lt;/code&gt; is a cool tool and it is very easy to set up and use. I find this approach very interesting. For now, the setup is working quite well and I did not see any drawbacks to it.&lt;/p&gt;

&lt;p&gt;The only different thing is that I will have to use the IP of my server instead of &lt;code&gt;localhost&lt;/code&gt; if I want to access services running on docker.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>linux</category>
    </item>
    <item>
      <title>My thoughts on completing the React Hooks workshop from Epic React by Kent C. Dodds</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Tue, 22 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/my-thoughts-on-completing-the-react-hooks-workshop-from-epic-react-by-kent-c-dodds-580k</link>
      <guid>https://dev.to/phiilu/my-thoughts-on-completing-the-react-hooks-workshop-from-epic-react-by-kent-c-dodds-580k</guid>
      <description>&lt;p&gt;I finally got some time and finished the second workshop of the &lt;a href="https://epicreact.dev/" rel="noopener noreferrer"&gt;Epic React by Kent C. Dodds&lt;/a&gt; course. It's been a while since I wrote &lt;a href="https://phiilu.com/my-thoughts-on-completing-the-react-fundamentals-workshop-from-epic-react" rel="noopener noreferrer"&gt;my last article about the first workshop&lt;/a&gt;, but here I am and trying to summarize my experience and learnings from the second workshop &lt;strong&gt;React Hooks&lt;/strong&gt;.&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%2Fimages.ctfassets.net%2Fhb3id6ag4raq%2F3IswRrwubtaNsid0ELR9C7%2Feb52b0ef48f74d654288b54d99553544%2FFlorian_Kapfenberger-epic-react-certificate.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%2Fimages.ctfassets.net%2Fhb3id6ag4raq%2F3IswRrwubtaNsid0ELR9C7%2Feb52b0ef48f74d654288b54d99553544%2FFlorian_Kapfenberger-epic-react-certificate.png" alt="Epic React Hooks Certificate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some people are already finishing the whole Epic React course and it took someone over &lt;a href="https://twitter.com/kentcdodds/status/1337432764451180544" rel="noopener noreferrer"&gt;3 weeks by spending 4 hours each day&lt;/a&gt;. That's &lt;strong&gt;84 hours&lt;/strong&gt; or 3 1/2 days spent on the whole course. I think I need to increase my speed on completing more workshops 😅 . This just shows how much content you get with this course.&lt;/p&gt;

&lt;p&gt;Now to my thoughts on the React Hooks workshop!&lt;/p&gt;

&lt;h2&gt;
  
  
  What will I learn?
&lt;/h2&gt;

&lt;p&gt;After completing the React Hooks workshop, you will be familiar with the most used hooks in React and how they work. &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useRef&lt;/code&gt;, and &lt;code&gt;useEffect&lt;/code&gt; will be explained and how you use them by programming a Tic-Tac-Toe game.&lt;/p&gt;

&lt;p&gt;Additionally, you will learn in which order hooks will run. Which &lt;code&gt;useEffect&lt;/code&gt; will run first? What about my lazy initializer in my &lt;code&gt;useState&lt;/code&gt;? When does my component get rendered? All these questions will get answered in this workshop.&lt;/p&gt;

&lt;p&gt;If you are still creating React components using classes and find hooks confusing, you will also get some exercises on how you can convert class components to function components with hooks.&lt;/p&gt;

&lt;p&gt;You will learn the concept of lifting state, so you will know when you need to move a certain state up to the parent.&lt;/p&gt;

&lt;p&gt;Finally, you will learn how you can fetch data from an external API with React hooks and learn about &lt;code&gt;ErrorBoundary&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I learn?
&lt;/h2&gt;

&lt;p&gt;I am already quite familiar with React hooks, but there are still somethings I learned in this workshop.&lt;/p&gt;

&lt;p&gt;I was not aware of the &lt;a href="https://kentcdodds.com/blog/use-state-lazy-initialization-and-function-updates" rel="noopener noreferrer"&gt;lazy initializer&lt;/a&gt; function that you can pass to the &lt;code&gt;useState&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Instead of doing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&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="nx"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getSomeComplexDefaultState&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can write it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&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="nx"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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;getSomeComplexDefaultState&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very useful for complex initial state. Using lazy initializers can in certain situations increase your web performance.&lt;/p&gt;

&lt;p&gt;The next thing I never looked up before, was the hook flow. Now I am more aware in which order my hooks in nested components run!&lt;/p&gt;

&lt;p&gt;Finally, in the last module about data fetching, I learned a lot about the React &lt;code&gt;ErrorBoundary&lt;/code&gt; component and using the &lt;code&gt;react-error-boundary&lt;/code&gt; &lt;a href="https://github.com/bvaughn/react-error-boundary" rel="noopener noreferrer"&gt;package&lt;/a&gt;. Kent C. Dodds has a &lt;a href="https://kentcdodds.com/blog/use-react-error-boundary-to-handle-errors-in-react" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; on his website, if you want to learn about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So after my second workshop, I have to say that I already know a lot about React, but at the same time, I don't. I am very glad that I bought this course and how much I gain from it. Knowing those little details is very valuable and can help you a lot when you encounter strange bugs in your code.&lt;/p&gt;

&lt;p&gt;The next workshop is &lt;strong&gt;Advanced React Hooks&lt;/strong&gt;. After finishing it you will hear from me again 😄&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Generate a sitemap for your Next.js site and submit it to Google</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Mon, 07 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/generate-a-sitemap-for-your-next-js-site-and-submit-it-to-google-9a0</link>
      <guid>https://dev.to/phiilu/generate-a-sitemap-for-your-next-js-site-and-submit-it-to-google-9a0</guid>
      <description>&lt;p&gt;One way to improve your SEO is by submitting a sitemap to Google. Sitemaps are an XML representation of your website and its pages. It allows search engines to know what pages your website has, without crawling each link on your page first. &lt;/p&gt;

&lt;p&gt;By submitting a sitemap to the &lt;a href="http://search.google.com/search-console"&gt;Google Search Console&lt;/a&gt; you can help Google to better identify your pages and you can see problems if some of the URLs are not being indexed. &lt;/p&gt;

&lt;p&gt;Most websites have sitemaps and you can easily find them by looking at the &lt;code&gt;robots.txt&lt;/code&gt; and searching for the &lt;code&gt;Sitemap&lt;/code&gt; entry. &lt;/p&gt;

&lt;p&gt;Here are a few sitemaps by popular websites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google: &lt;a href="https://www.google.com/sitemap.xml"&gt;https://www.google.com/sitemap.xml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Verge: &lt;a href="https://www.theverge.com/sitemaps"&gt;https://www.theverge.com/sitemaps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NY Times: &lt;a href="https://www.nytimes.com/sitemaps/new/news.xml.gz"&gt;https://www.nytimes.com/sitemaps/new/news.xml.gz&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Generating the sitemap.xml
&lt;/h2&gt;

&lt;p&gt;With a CMS like WordPress, you usually get a sitemap generated by default, but if you created your site using Next.js you need to take care of this yourself. &lt;/p&gt;

&lt;p&gt;Gladly this is not a too hard problem to solve and you probably can just copy my code with some adjustments and you should be fine.&lt;/p&gt;

&lt;p&gt;As in my post where I described &lt;a href="https://phiilu.com/generate-rss-feeds-for-your-static-next-js-blog"&gt;how to generate an RSS feed&lt;/a&gt; with Next.js, I will create a new file inside my &lt;code&gt;lib&lt;/code&gt; folder and name it &lt;code&gt;sitemap.js&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here is the content of my &lt;code&gt;sitemap.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;globby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;globby&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="nx"&gt;contentful&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;./contentful&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&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;SitemapStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamToPromise&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;sitemap&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;Readable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;stream&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="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// pages that should not be in the sitemap&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blocklist&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;/newsletter-success&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;/404&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateSitemap&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;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="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;development&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&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;baseUrl&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="nx"&gt;BASE_URL&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;pages&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;globby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/pages/**/*{.js,.mdx}&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;!src/pages/**/[*&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;!src/pages/_*.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;!src/pages/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// normal page routes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&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="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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pages&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="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.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="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.mdx&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="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/&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="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/index&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;url&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="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;filter&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;blocklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// post routes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&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;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-fields.publishedDate&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="nx"&gt;postLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&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="na"&gt;url&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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="c1"&gt;// tag routes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tags&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&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="nx"&gt;tagLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/tag/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;pageLinks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;postLinks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;tagLinks&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;stream&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;SitemapStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&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;xml&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;streamToPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&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;data&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/sitemap.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;generateSitemap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am using two external dependencies in this code, &lt;a href="https://github.com/sindresorhus/globby"&gt;globby&lt;/a&gt; and &lt;a href="https://github.com/ekalinin/sitemap.js"&gt;sitemap&lt;/a&gt;, so you have to add them to your &lt;code&gt;package.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; globby sitemap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's breakdown the code into smaller pieces and let me explain what we are doing here.&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;// if (process.env.NODE_ENV === 'development') {&lt;/span&gt;
&lt;span class="c1"&gt;//   return;&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&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="nx"&gt;BASE_URL&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;pages&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;globby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/pages/**/*{.js,.mdx}&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="s2"&gt;!src/pages/**/[*&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="s2"&gt;!src/pages/_*.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="s2"&gt;!src/pages/api&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First of all, we don't want to run this script when we are in development mode. For now, it is commented, because we need to test our function first, but after it is working you can uncomment it and it will only run in production.&lt;/p&gt;

&lt;p&gt;Next, I defined some variables. &lt;code&gt;baseUrl&lt;/code&gt; will be your domain of the website like in my case the value will be &lt;code&gt;https://phiilu.com&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I am using a package named &lt;code&gt;globby&lt;/code&gt; to get all the pages from the &lt;code&gt;pages&lt;/code&gt; folder using a &lt;a href="https://en.wikipedia.org/wiki/Glob_%28programming%29"&gt;glob&lt;/a&gt;. It will return all the pages that are either &lt;code&gt;.js&lt;/code&gt; or &lt;code&gt;.mdx&lt;/code&gt; and not starting with &lt;code&gt;[&lt;/code&gt; or &lt;code&gt;_&lt;/code&gt; like you would use for dynamic pages or the &lt;code&gt;_app.js&lt;/code&gt;. It won't return the files that are in the &lt;code&gt;pages/api&lt;/code&gt; folder too. &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;blocklist&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="s2"&gt;/newsletter-success&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="s2"&gt;/404&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// normal page routes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&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="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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pages&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="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.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="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.mdx&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="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/&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="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/index&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;url&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="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;filter&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;blocklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I am creating an array of &lt;code&gt;pageLinks&lt;/code&gt; that will be used to generate the sitemap. We only want to have the name of the file, so we are replacing the strings we won't need with an empty string. You can adjust the values for &lt;code&gt;changefreq&lt;/code&gt; or &lt;code&gt;priority&lt;/code&gt; for your needs. Those values will tell the search engine how often the page is changing and how important it is. &lt;/p&gt;

&lt;p&gt;I also have some pages that I don't want to have inside the &lt;code&gt;sitemap.xml&lt;/code&gt;, so I created a &lt;code&gt;blocklist&lt;/code&gt; array and filter the pages that are in that array. &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;// post routes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&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;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-fields.publishedDate&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&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="na"&gt;url&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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// tag routes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tags&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tag&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="nx"&gt;tagLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/tag/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;This part may not be necessary for you, depending on if you are using a headless CMS or not. I am using Contentful to store my posts and tags so I will have to fetch the data from there and create the &lt;code&gt;postLinks&lt;/code&gt; and &lt;code&gt;tagLinks&lt;/code&gt; array which will have the same structure as the &lt;code&gt;pageLinks&lt;/code&gt; array. &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;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;pageLinks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;postLinks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;tagLinks&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;stream&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;SitemapStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&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;xml&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;streamToPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&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;data&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public/sitemap.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last but not least, I created the final &lt;code&gt;links&lt;/code&gt; array which will contain all links that I want to have inside my &lt;code&gt;sitemap.xml&lt;/code&gt;. To generate the &lt;code&gt;sitemap.xml&lt;/code&gt; I am using a package named &lt;code&gt;sitemap&lt;/code&gt; that will be doing the hard work of generating the correct XML content. Finally, the XML data will be saved to a file inside the &lt;code&gt;public&lt;/code&gt; folder, so Next.js can serve it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Generate the sitemap during the build
&lt;/h2&gt;

&lt;p&gt;I will be using the same approach as with generating the RSS feeds. Importing the &lt;code&gt;generateSitemap&lt;/code&gt; function in the pages/index.js file and adding it to the getStaticProps will make sure Next.js will call this function during the build of the &lt;code&gt;pages/index.js&lt;/code&gt; page and generate the sitemap.&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;generateRssFeed&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;generateSitemap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ogImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&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;h2&gt;
  
  
  Submit the sitemap to Google
&lt;/h2&gt;

&lt;p&gt;Finally, now that you have your &lt;code&gt;sitemap.xml&lt;/code&gt; file, you can submit this file to the &lt;a href="http://search.google.com/search-console"&gt;Google Search Console&lt;/a&gt;. After adding your domain to the Search Console you just have to go to the Sitemap page and submit the URL where your &lt;code&gt;sitemap.xml&lt;/code&gt; file is available. After a few days, you will see how many pages of your sitemap are being indexed by Google and which ones will have some problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/1br9i7J6HyuujZJcCnSmUl/7d6298c34b6940d265bcb0481e8d78e1/Screenshot_2020-12-07_at_15.16.38.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/1br9i7J6HyuujZJcCnSmUl/7d6298c34b6940d265bcb0481e8d78e1/Screenshot_2020-12-07_at_15.16.38.png" alt="Submitting the sitemap.xml to the Google Search Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Generating a sitemap can be very useful for search engines and having one can only be a benefit. If you are using Next.js you need to take care of this yourself, but as I have shown you in this tutorial it is not very difficult as the hard part will be done by existing packages anyway.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>seo</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Generate RSS feeds for your static Next.js blog</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Fri, 20 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/phiilu/generate-rss-feeds-for-your-static-next-js-blog-4198</link>
      <guid>https://dev.to/phiilu/generate-rss-feeds-for-your-static-next-js-blog-4198</guid>
      <description>&lt;p&gt;On the internet, there are many ways to allow visitors to get updated for new content on your blog. A pretty common option is to offer a &lt;strong&gt;Really Simple Syndication&lt;/strong&gt; or more commonly known as &lt;strong&gt;RSS&lt;/strong&gt; feed. &lt;/p&gt;

&lt;p&gt;An &lt;a href="https://en.wikipedia.org/wiki/RSS"&gt;RSS&lt;/a&gt; feed is a standardized XML file that contains information about the website and about all articles. Many people like to use RSS readers like &lt;a href="https://feedly.com/"&gt;Feedly&lt;/a&gt; or &lt;a href="https://feeder.co/"&gt;Feeder&lt;/a&gt; to read blog posts, so it's a good idea to offer it!&lt;/p&gt;

&lt;p&gt;Next to RSS there also exists the &lt;a href="https://en.wikipedia.org/wiki/Atom_(Web_standard)"&gt;Atom&lt;/a&gt; and &lt;a href="https://jsonfeed.org/"&gt;JSON feed&lt;/a&gt;. Atom should be the successor of RSS and has some improvements over RSS. JSON feed is basically RSS, but instead of using XML, it uses JSON.&lt;/p&gt;

&lt;p&gt;In this tutorial, I will show you how to generate all 3 feeds in Javascript with the help of the &lt;a href="https://github.com/jpmonette/feed"&gt;&lt;code&gt;feed&lt;/code&gt;&lt;/a&gt; package and add them to your static Next.js site. You can see the final results of the different feeds by clicking the link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://phiilu.com/rss/feed.xml"&gt;RSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://phiilu.com/rss/atom.xml"&gt;Atom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://phiilu.com/rss/feed.json"&gt;JSON Feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Generating RSS feeds
&lt;/h2&gt;

&lt;p&gt;Inside the Next.js project create a new file where you want to put all the logic for generating RSS feeds. I created my file inside a &lt;code&gt;lib&lt;/code&gt; folder and named it &lt;code&gt;rss.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, add the &lt;code&gt;feed&lt;/code&gt; package from npm to your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add feed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before writing any code, let's think about what we want to do. There are 3 steps we need to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new Feed instance and initialize it with common data about the blog&lt;/li&gt;
&lt;li&gt;loop over all articles and add them to the feed&lt;/li&gt;
&lt;li&gt;generate the web feeds that you want and store them in files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds not too complicated so let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new Feed instance
&lt;/h3&gt;

&lt;p&gt;Inside the &lt;code&gt;rss.js&lt;/code&gt; file create a new function and name it something like &lt;code&gt;generateRssFeed&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Next, we want to initialize the Feed instance.&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;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://phiilu.com&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="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&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;feed&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;Feed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Phiilu's Blog`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to my blog!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;image&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/images/logo.svg`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;favicon&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/favicon.ico`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;copyright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`All rights reserved &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;, Florian Kapfenberger`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Next.js using Feed for Node.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;feedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rss2&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/feed.xml`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;json&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/feed.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;atom&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/atom.xml`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;author&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;This is meta-information about your blog. You can define the &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;favicon&lt;/code&gt;, and other data that will be used inside RSS readers. &lt;/p&gt;

&lt;h3&gt;
  
  
  Add articles to the feed
&lt;/h3&gt;

&lt;p&gt;Now you need to gather all articles from your blog and add them to the feed. &lt;/p&gt;

&lt;p&gt;I am using &lt;a href="https://www.contentful.com/"&gt;Contentful&lt;/a&gt; as my headless CMS, so I am fetching the data using the Contentful API. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are using MDX you probably need to read the files from your disk and extract the frontmatter data.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Florian Kapfenberger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hey@phiilu.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://twitter.com/phiilu&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&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;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-fields.publishedDate&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;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&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;url&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;baseUrl&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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&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="na"&gt;link&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;contributor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawDate&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;p&gt;Here I am fetching my articles using the Contentful API and adding them to the &lt;code&gt;Feed&lt;/code&gt; instance. My articles are written in Markdown, but RSS feeds expect them to be in HTML. This is why I am using the &lt;code&gt;markdown&lt;/code&gt; package to transform my Markdown into HTML for the &lt;code&gt;content&lt;/code&gt; property.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate the different feed files
&lt;/h3&gt;

&lt;p&gt;Next.js will serve files from the &lt;code&gt;public&lt;/code&gt; folder, so this is the location where we want to store the different feeds.&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public/rss&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;recursive&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public/rss/feed.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rss2&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public/rss/atom.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atom1&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public/rss/feed.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json1&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to &lt;code&gt;feed&lt;/code&gt; we can generate all three formats by just calling the correct function!&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps summary
&lt;/h3&gt;

&lt;p&gt;After these 3 steps, your &lt;code&gt;rss.js&lt;/code&gt; file should look similar to this one.&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;Feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;feed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Feed&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;contentful&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;./contentful&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&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;markdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;markdown&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateRssFeed&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;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="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;development&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&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;baseUrl&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="nx"&gt;BASE_URL&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;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&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;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Florian Kapfenberger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hey@phiilu.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://twitter.com/phiilu&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="nx"&gt;feed&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;Feed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Phiilu's Blog`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome to my blog!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;image&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/images/logo.svg`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;favicon&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/favicon.ico`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;copyright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`All rights reserved &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;, Florian Kapfenberger`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Next.js using Feed for Node.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;feedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rss2&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/feed.xml`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;json&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/feed.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;atom&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss/atom.xml`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;author&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;posts&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&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;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-fields.publishedDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&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;url&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;baseUrl&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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;id&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="na"&gt;link&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;contributor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawDate&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/rss&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;recursive&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/rss/feed.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rss2&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/rss/atom.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atom1&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/rss/feed.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json1&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;generateRssFeed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generate RSS feeds during the build
&lt;/h2&gt;

&lt;p&gt;Now that we have our little utility function to generate the RSS feeds, we need to define when the RSS feeds should get generated. &lt;/p&gt;

&lt;p&gt;To keep it simple I am importing this function in the &lt;code&gt;pages/index.js&lt;/code&gt; file and adding it to the &lt;code&gt;getStaticProps&lt;/code&gt;. Doing it this way will make sure Next.js will call this function during the build of the &lt;code&gt;pages/index.js&lt;/code&gt; page and generate the feeds.&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;generateRssFeed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ogImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&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;p&gt;The cleaner way probably is to call this function somewhere in the webpack config by using a plugin or something like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There is a whole market out there for RSS readers and offering RSS for your readers can be very valuable to grow your audience.&lt;/p&gt;

&lt;p&gt;Thanks to the &lt;code&gt;feed&lt;/code&gt; package it is very easy to generate RSS feeds with Javascript too. &lt;/p&gt;

&lt;p&gt;What do you think about RSS? Do you offer RSS on your blog? Are you using RSS readers and if so which one do you use? I am very interested in what you think, lets chat in the comments below!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>node</category>
    </item>
    <item>
      <title>Here are 6 Google Analytics alternatives that care about your privacy</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Mon, 09 Nov 2020 21:44:41 +0000</pubDate>
      <link>https://dev.to/phiilu/here-are-6-google-analytics-alternatives-that-care-about-your-privacy-1em3</link>
      <guid>https://dev.to/phiilu/here-are-6-google-analytics-alternatives-that-care-about-your-privacy-1em3</guid>
      <description>&lt;p&gt;&lt;span&gt;Photo by &lt;a href="https://unsplash.com/@edhoradic?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Edho Pratama&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/google-analytics?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;




&lt;p&gt;I tried out many analytics SaaS on my blog to find a great alternative for my tracking needs. I was using Google Analytics before, but now I want to use something that cares about my and other user's privacy.&lt;/p&gt;

&lt;p&gt;My feature requests are very simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tracks page views without using cookies&lt;/li&gt;
&lt;li&gt;privacy-focused&lt;/li&gt;
&lt;li&gt;can track events&lt;/li&gt;
&lt;li&gt;offers a way to use a custom domain to deny ad blockers&lt;/li&gt;
&lt;li&gt;(optional) possibility to share the dashboard via a link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most alternatives I found offer (most of) those features and even more. Before going over each alternative, I want to talk about why you might want to get rid of Google Analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you should not use Google Analytics
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Privacy.&lt;/strong&gt; Google Analytics is a free service from Google. When something is free, you probably will be paying with something else. In Google's case, you will be paying with the data that you collect for them. Google is a big ad company that generates money by using your data. So this is one big reason not to use Google Analytics. I try to reduce my services that depend on Google and I always look for alternatives. &lt;a href="https://twitter.com/levelsio"&gt;Pieter Levels&lt;/a&gt; created a website where you can find alternatives for Google services called &lt;a href="https://nomoregoogle.com/"&gt;No More Google&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cookies.&lt;/strong&gt; Another reason why you might not want to use Google Analytics is that you will have to show those ugly cookie banners. If you are using services that put cookies on your site you will have to show those cookie banners to stay GDPR compliant. You will also have to put in place the option to opt-out of it and have a policy page. It's not that difficult to implement, but you could skip it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complex Dashboard.&lt;/strong&gt; Google Analytics is way more than just tracking pageviews, therefore it has lots of features and the dashboard is not that easy to use. If you only want to track page views and maybe some events then I think Google Analytics is overkill for your use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ad-Blockers.&lt;/strong&gt; If you use Google Analytics you will not track all page views, because of the well-known domain name that it is blocked by most ad blockers. With other services, you can use a custom domain that is not blocked by ad blockers and you will get way more analytics data.&lt;/p&gt;




&lt;p&gt;In the next sections, I want to give you an overview of 6 different analytics services that I can recommend without blinking an eye. Some of them are free to use and open-source, others will require a subscription. &lt;/p&gt;

&lt;h2&gt;
  
  
  Plausible Analytics
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://plausible.io/"&gt;https://plausible.io/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simple and privacy-friendly alternative to Google Analytics&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plausible Analytics is one of my favorite Analytics services I have used. &lt;/p&gt;

&lt;p&gt;The UI is very modern and clean, it is privacy-friendly, can track multiple websites in one account, you can &lt;a href="https://docs.plausible.io/self-hosting/"&gt;self-host&lt;/a&gt; it because it is &lt;a href="https://github.com/plausible/analytics"&gt;open source&lt;/a&gt;, it allows the use of custom domains, sends you weekly or monthly reports and much more.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/tClWlK2Wk8YxnZzelQq3l/dad4d975b556a1702daa15e0c7ff0c82/Screenshot_2020-11-04_at_21.10.14.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/tClWlK2Wk8YxnZzelQq3l/dad4d975b556a1702daa15e0c7ff0c82/Screenshot_2020-11-04_at_21.10.14.png" alt="Plausible Analytics 7-days" title="7 days Report of phiilu.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It ticks almost every checkbox of the features I need and if it is not currently implemented it will be soon. Plausible Analytics is working on more features and they have a &lt;a href="https://github.com/plausible/analytics/projects/1"&gt;public roadmap&lt;/a&gt; where you can see what will be added next!&lt;/p&gt;

&lt;p&gt;I like to host services myself therefore I love that Plausible is open source and that I can host it myself. Of course, with self-hosting, I need to take care that the server is always online and the database is backed up regularly. &lt;/p&gt;

&lt;p&gt;If I don't want to do that, I can let Plausible take care of it for a very fair price depending on the amount of traffic you have. They even offer a free trial of 30 days without requiring a credit card. &lt;/p&gt;

&lt;h2&gt;
  
  
  Fathom
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://usefathom.com/ref/SAJGHU"&gt;https://usefathom.com/&lt;/a&gt; (affiliate link)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your website analytics should be simple, fast and privacy-focused&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fathom is one of the first analytics SaaS that focused on privacy. &lt;/p&gt;

&lt;p&gt;It offers a clean and easy to use dashboard with only the information that you will need. You can use your Fathom account for multiple websites, the pricing is fair, you can use custom domains and you can track events by creating goals.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/7EEZoLbMU6NmsGnoJvykT8/81839ba5790494c6a95a1c5d4cd7d056/Screenshot_2020-11-04_at_21.08.19.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/7EEZoLbMU6NmsGnoJvykT8/81839ba5790494c6a95a1c5d4cd7d056/Screenshot_2020-11-04_at_21.08.19.png" alt="Fathom 7-days" title="7 days Report of phiilu.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They are currently working on new branding and &lt;a href="https://twitter.com/usefathom/status/1319310149647937536"&gt;as it looks like&lt;/a&gt; a new dashboard with additional features like API access. &lt;/p&gt;

&lt;p&gt;Additional to analytics Fathom also provides website monitoring. You can get notified if your website ever goes offline and that is included with your account for FREE!&lt;/p&gt;

&lt;p&gt;Recently Fathom published the &lt;a href="https://usephantom.com/"&gt;Phantom Analyzer&lt;/a&gt;. A simple tool where you can check if a site is using some shady trackers or if your site is trackers free.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/1bE9MaSnfSxFWH7iuJYcj2/73d40052d493ad36b741bb10b059de96/Screenshot_2020-11-04_at_21.48.43.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/1bE9MaSnfSxFWH7iuJYcj2/73d40052d493ad36b741bb10b059de96/Screenshot_2020-11-04_at_21.48.43.png" alt="Phantom Analyzer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you care about privacy then I would suggest that you check out Fathom. They offer 7 days free trial and if you decide to delete your account, they make sure that your data is gone for good!&lt;/p&gt;

&lt;h2&gt;
  
  
  Umami
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://umami.is/"&gt;https://umami.is/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;own your website analytics&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Umami is an open-source alternative to Google Analytics developed by &lt;a href="https://twitter.com/caozilla"&gt;Mike Cao&lt;/a&gt; that you can host yourself.&lt;/p&gt;

&lt;p&gt;Umami offers simple page and event tracking with a beautiful minimalistic UI. You can add as many websites as you like. Create a shareable link for the analytics and see real-time visitors. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/6m0v9HixM56yBOeWhgSP9P/5eef4d612538564bc4e7bf7ccd7288ae/Screenshot_2020-11-04_at_21.10.39.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/6m0v9HixM56yBOeWhgSP9P/5eef4d612538564bc4e7bf7ccd7288ae/Screenshot_2020-11-04_at_21.10.39.png" alt="Umami 7-days" title="7 days Report of phiilu.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I like how simple and clean the dashboard from Umami is.  New features and improvements are added regularly. Hosting is very easy too and documented well!  &lt;/p&gt;

&lt;p&gt;Hosting the analytics service yourself will allow you full control of the data that is tracked of your visitors. As I already said if tracking data is critical for you, you should probably invest some time into setting up some backup strategies. Therefore Umami is "free", but you will have to pay with your own time and stress I guess 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Analytics
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://referral.simpleanalytics.com/phiilu"&gt;https://simpleanalytics.com/&lt;/a&gt; (affiliate link)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simple, clean, and friendly analytics&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I found Simple Analytics via Twitter and thought I might check it out too. &lt;/p&gt;

&lt;p&gt;Simple Analytics offers privacy-friendly analytics that won't sell your data. It has a strong focus on being privacy-friendly and &lt;a href="https://docs.simpleanalytics.com/what-we-collect?ref=simpleanalytics.com"&gt;transparent&lt;/a&gt; with what it will track and store.&lt;br&gt;
&lt;a href="//images.ctfassets.net/hb3id6ag4raq/1dJrVz6iYkXWuBNsENZU7v/4dcef2dc5222491006217897cde49070/Screenshot_2020-11-04_at_21.08.49.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/1dJrVz6iYkXWuBNsENZU7v/4dcef2dc5222491006217897cde49070/Screenshot_2020-11-04_at_21.08.49.png" alt="Simple Analytics 7-days" title="7 days Report of phiilu.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see all the important information you need at a glance with a beautiful dashboard. Event tracking is possible but stated as being highly experimental.&lt;/p&gt;

&lt;p&gt;Simple Analytics have a &lt;a href="https://simpleanalytics.com/roadmap"&gt;public roadmap&lt;/a&gt; too, where you can see what will be implemented next and even request missing features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitbee
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://splitbee.io/"&gt;https://splitbee.io/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your friendly all-in-one analytics &amp;amp; conversion tool.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Splitbee is a bit different than the other analytics SaaS, as it offers additional features too that are not only focusing on analytics.&lt;/p&gt;

&lt;p&gt;Splitbee offers a modern UI where you can see your top pages and top sources in addition to how many unique users you got in that period.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/6upzuNTkklX4ln0KDinmyi/439af49405408544b829de71f3b39827/Screenshot_2020-11-04_at_21.11.01.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/6upzuNTkklX4ln0KDinmyi/439af49405408544b829de71f3b39827/Screenshot_2020-11-04_at_21.11.01.png" alt="Splitbee 7-days" title="7 days Report of phiilu.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additional to analytics you can do automation where you can send emails or trigger a webhook when a certain event or pageview happens.&lt;/p&gt;

&lt;p&gt;You can do "experiments" like A/B testing or Redirect testing.&lt;/p&gt;

&lt;p&gt;If you are using Splitbee with your app where users sign in you can identify users so you can track them and see how they interact with your app.&lt;/p&gt;

&lt;p&gt;I haven't used many of the offered features besides page and event tracking. I could add automation where I send myself a message when some successfully sign up for my newsletter, which is neat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matomo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://matomo.org/"&gt;https://matomo.org/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google Analytics alternative that protects your data and your customers' privacy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Matomo is probably the most known alternative to Google Analytics. I have used it for some time, but I am not a big fan of it because it offers way too much functionality for my use case. &lt;/p&gt;

&lt;p&gt;It is probably the most mature alternative to Google Analytics as it offers a great selection of plugins and settings. If you just need to track page views with some events then I think Matomo might not be the best choice. &lt;/p&gt;

&lt;p&gt;Like Plausible Analytics and Umami, you can host Matomo yourself which I think is great!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You see that there are many great alternatives for Google Analytics out there that focus on privacy and won't sell your data. &lt;/p&gt;

&lt;p&gt;I think there is no need to continue using Google Analytics for simple projects that won't use any other Google Services.&lt;/p&gt;

&lt;p&gt;If you care about privacy you should probably switch to one of the mentioned alternatives. &lt;/p&gt;

&lt;p&gt;Every single product I mentioned offer great features and I can recommend them all. I wish I could use all of them, but that would be counterproductive to bloat a website with all possible trackers 😁&lt;/p&gt;

&lt;p&gt;I think Plausible Analytics is the one that speaks to me the most. I like the UI and the reason that it is open source. They have a great product and offer a good service with their hosted solution which allowed them to &lt;a href="https://www.indiehackers.com/product/plausible-insights/hit-6k-mrr-and-got-featured-in-techcrunch--MKJIZLay_rToE1kxCO8"&gt;reach an MRR of over 6K$&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do you still use Google Analytics and if so are you thinking about using an alternative? Let me know what you think in the comments I am very interested to hear what you have to share!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>saas</category>
      <category>analytics</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Generate Open Graph images for your static Next.js site</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Fri, 30 Oct 2020 21:31:21 +0000</pubDate>
      <link>https://dev.to/phiilu/generate-open-graph-images-for-your-static-next-js-site-and-host-them-locally-18ll</link>
      <guid>https://dev.to/phiilu/generate-open-graph-images-for-your-static-next-js-site-and-host-them-locally-18ll</guid>
      <description>&lt;p&gt;You probably know those fancy images that you see on Twitter or Slack when someone shares a link to a website. The information that you see is meta tags stored inside HTML. They even have a fancy name and belong to the &lt;a href="https://ogp.me/"&gt;Open Graph protocol&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Here is an example from my blog's homepage:&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:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Home"&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;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Welcome to my blog!"&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;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"website"&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;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://phiilu.com/"&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;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://phiilu.com/images/og/977261ad2dded809bf3f4bdcf453f416.png"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those meta tags are very easy to put onto your site, but the image one can be a bit tricky because we need real images that are different for each page we have!&lt;/p&gt;

&lt;p&gt;In this post, I want to show you how you can design your own images and generate them on build time for your static Next.js site!&lt;/p&gt;

&lt;p&gt;The principle is very simple. We have a webpage where we generate an HTML site that looks like the OG image we want to display when somebody shares our site. Next, we use Next.js build process to crawl this site, generate an image of the webpage, and save it somewhere, where Next.js can access it.&lt;/p&gt;

&lt;p&gt;So let's start with building our OG Image using CSS!&lt;/p&gt;
&lt;h2&gt;
  
  
  Create your OG image page
&lt;/h2&gt;

&lt;p&gt;In my case, I generated a separate app with the only purpose of generating the images and displaying them. You could also add the functionality we are going to write into your existing app, but make sure to deploy the code we write in this section first! &lt;/p&gt;

&lt;p&gt;What we are going to write is basically a webpage that takes some query parameters and generates the image we want. For example, if we access our app with this URL (go check it out)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://og-image.phiilu.com/phiilu.com?title=Hello%20World&amp;amp;url=https://phiilu.com/hello-world"&gt;https://og-image.phiilu.com/phiilu.com?title=Hello%20World&amp;amp;url=https://phiilu.com/hello-world&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;we get a webpage which generates this image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--degiPNI3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://phiilu.com/images/og/a761550273f761b366739c6c04d76850.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--degiPNI3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://phiilu.com/images/og/a761550273f761b366739c6c04d76850.png" alt="Hello World generated OG image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So all you need is a simple route and a component that looks similar to this one.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;GoogleFonts&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-google-fonts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Heading&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@components/Heading/Heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// The function `getFontSize` will increase or decrease the // font size of the title depending on its length.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getFontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&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;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;55&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="s2"&gt;`text-6xl`&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;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;32&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="s2"&gt;`text-7xl`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`text-8xl`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example URL: http://localhost:3000/phiilu.com?title=Hello%20mein%20Name%20ist%20Florian!&amp;amp;url=https://phiilu.com/hello-world&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PhiiluCom&lt;/span&gt; &lt;span class="o"&gt;=&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRouter&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;searchParams&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;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\?&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&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;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&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;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;linkURL&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GoogleFonts&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&amp;amp;family=Source+Sans+Pro:wght@300;400;600;700&amp;amp;display=swap"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"relative flex flex-col justify-between px-8 pt-24 pb-16 space-y-8 bg-indigo-100 border-indigo-500 shadow-md"&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;1200&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;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;borderWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"absolute top-0 right-0 mt-6 mr-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'/images/phiilu.com-logo.svg'&lt;/span&gt;
            &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"96"&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"96"&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-24 h-24"&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"max-w-screen-lg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Heading&lt;/span&gt;
            &lt;span class="na"&gt;noMargin&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;getFontSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; text-indigo-800`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-between"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center space-x-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
              &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://pbs.twimg.com/profile_images/1220392920538386432/NuYyL5b5_400x400.jpg"&lt;/span&gt;
              &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Florian Kapfenberger"&lt;/span&gt;
              &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-none w-32 h-32 rounded-full shadow-md handsome"&lt;/span&gt;
            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col text-indigo-900"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-4xl font-semibold font-open-sans"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Phiilu.com&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl font-open-sans"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;linkURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;PhiiluCom&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Of course, I am using Next.js here as well. I am using Tailwind, but you can use plain CSS or any other framework to style the page. &lt;/p&gt;

&lt;p&gt;You can style your image like you want to, the only important part is that your OG image has the right dimensions. I am using &lt;code&gt;1200&lt;/code&gt; for the width and &lt;code&gt;630&lt;/code&gt; as the height as this is what most sites recommend. &lt;/p&gt;

&lt;p&gt;To be fair those are not the perfect values for Twitter cards, but there are many &lt;a href="https://iamturns.com/open-graph-image-size/"&gt;different recommendations&lt;/a&gt; out there. Maybe in the future, I might generate different formats for different services.&lt;/p&gt;

&lt;p&gt;To make the image dynamic I am using &lt;code&gt;URLSearchParams&lt;/code&gt; to get the values from the URL query. If you don't know what &lt;code&gt;URLSearchParams&lt;/code&gt; is you can &lt;a href="https://phiilu.com/dealing-with-url-query-parameters-in-javascript-using-urlsearchparams"&gt;check out my blog post&lt;/a&gt; where I explain how it works.&lt;/p&gt;

&lt;p&gt;Now that your design is done and you are happy with it, you need to deploy your app somewhere. If you are using Next.js I would recommend Vercel or Netlify. &lt;/p&gt;

&lt;p&gt;You can check out &lt;a href="https://github.com/phiilu/og-image-app"&gt;the full source code on GitHub&lt;/a&gt; if you want.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/phiilu"&gt;
        phiilu
      &lt;/a&gt; / &lt;a href="https://github.com/phiilu/og-image-app"&gt;
        og-image-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This simple React app builds pages which will be used as OG images.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  The OG image generator function
&lt;/h2&gt;

&lt;p&gt;Now that we have our little OG image app ready, we can finally continue and generate our OG images!&lt;/p&gt;

&lt;p&gt;First, create a new file where you want to put your code. I chose to put mine into a &lt;code&gt;lib&lt;/code&gt; folder and named the file &lt;code&gt;getOgImage.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We basically need to do 3 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;check if we already have the OG image and if yes just return the location&lt;/li&gt;
&lt;li&gt;if not, we need to open the browser with the correct query parameters and take a screenshot of the page&lt;/li&gt;
&lt;li&gt;store the screenshot where Next.js can use it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All these steps translate into this code:&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;playwright&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;playwright-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;createHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;crypto&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="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getOgImage&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;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://og-image.phiilu.com&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&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="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;development&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og image will be generated in 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&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;baseUrl&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="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;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&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;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&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="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;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launchChromium&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="kc"&gt;true&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;ogImageDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`./public/images/og`&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;imagePath&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;ogImageDir&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="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&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;publicPath&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;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="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/images/og/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imagePath&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;publicPath&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// file does not exists, so we create it&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="nx"&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="nx"&gt;setViewportSize&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;1200&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;630&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="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="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;networkidle&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="nx"&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;png&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ogImageDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imagePath&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;publicPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;getOgImage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's take a deeper look into what some of these lines mean.&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getOgImage&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;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://og-image.phiilu.com&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="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;Our function will take 2 parameters. The first one is the path with the query parameters that will generate the OG image like &lt;code&gt;/phiilu.com?title=Hello%20World&amp;amp;url=https://phiilu.com/hello-world&lt;/code&gt;. The second one is optional, which is the &lt;code&gt;baseUrl&lt;/code&gt; of our OG image app.&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&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="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;development&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og image will be generated in 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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;During the development of this feature, you might want to comment this block otherwise, the images are only generated in production. This is usually what you want because it can slow down your development workflow.&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;url&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;baseUrl&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="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;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&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;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&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="nx"&gt;ogImageDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`./public/images/og`&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;imagePath&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;ogImageDir&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="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&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;publicPath&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;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="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/images/og/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we define our OG image &lt;code&gt;url&lt;/code&gt; with the two parameters from the function. &lt;/p&gt;

&lt;p&gt;Then we create a &lt;code&gt;hash&lt;/code&gt; of this &lt;code&gt;url&lt;/code&gt;. The  &lt;code&gt;hash&lt;/code&gt; will be the filename of the image and lets us decide if we already generated an image for this request or not. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ogImageDir&lt;/code&gt; is the directory where we will store the OG images for Next.js. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;imagePath&lt;/code&gt; is the file path where we will save the image. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;publicPath&lt;/code&gt; is the absolute URL where our image will be available and the &lt;code&gt;content&lt;/code&gt; value for the &lt;code&gt;&amp;lt;meta name="og:image" /&amp;gt;&lt;/code&gt; tag.&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imagePath&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;publicPath&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// file does not exists, so we create it&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we use the &lt;code&gt;fs.statSync&lt;/code&gt; method to check if we already have an image for the requested URL. If the file already exists we will return the &lt;code&gt;publicPath&lt;/code&gt; otherwise the method will throw an error and we can continue with our logic.&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;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;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launchChromium&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="kc"&gt;true&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="nx"&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="nx"&gt;setViewportSize&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;1200&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;630&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="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="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;networkidle&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="nx"&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;png&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is probably the most interesting part. Here we are using &lt;br&gt;
&lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; to control a headless browser.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Playwright is a great library to scrap webpages and automate your browser! &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we use Playwright to create a new empty page in the browser and set the viewport to exactly the dimensions we have specified in our OG image app. Next, we will browse to the &lt;code&gt;url&lt;/code&gt; and wait until the network has become idle. Now we use the very cool method &lt;code&gt;screenshot&lt;/code&gt; to generate a screenshot of the page and save the &lt;code&gt;buffer&lt;/code&gt; inside a variable. When we are finished we can close the browser.&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ogImageDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imagePath&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;recursive: true&lt;/code&gt; means if the directory already exists it will not be created again. This works exactly like the command &lt;code&gt;mkdir -p DIR&lt;/code&gt; in the terminal &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last but not least we create the &lt;code&gt;ogImageDir&lt;/code&gt; and create a new file with the contents of the saved &lt;code&gt;buffer&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;DONE! We have successfully generated the OG image and saved it where Next.js can serve it!&lt;/p&gt;
&lt;h2&gt;
  
  
  Generate the OG images during the build
&lt;/h2&gt;

&lt;p&gt;The last thing that is missing is that we call the function inside our pages. &lt;/p&gt;

&lt;p&gt;Go to a page where you want to generate the OG image and call the function inside the &lt;code&gt;getStaticProps&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;This is what my &lt;code&gt;getStaticProps&lt;/code&gt; in the &lt;code&gt;[slug].js&lt;/code&gt; file inside the &lt;code&gt;pages&lt;/code&gt; folder looks like:&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&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;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&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;ogImage&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;getOgImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/phiilu.com?title=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;url=&lt;/span&gt;&lt;span class="p"&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="nx"&gt;BASE_URL&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="nx"&gt;slug&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ogImage&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;p&gt;Now you have the &lt;code&gt;ogImage&lt;/code&gt; prop available inside the page component and we can render the &lt;code&gt;&amp;lt;meta /&amp;gt;&lt;/code&gt; tag inside the &lt;code&gt;Head&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/head&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="nx"&gt;PostDetails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ogImage&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&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="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ogImage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;some parts of the component have been skipped, to make the example easier&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My blog at &lt;a href="https://phiilu.com"&gt;phiilu.com&lt;/a&gt; is open-source, if you want you can have a look at the &lt;a href="https://github.com/phiilu/phiilu.com"&gt;whole repo here&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/phiilu"&gt;
        phiilu
      &lt;/a&gt; / &lt;a href="https://github.com/phiilu/phiilu.com"&gt;
        phiilu.com
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Personal Website
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That's all you need to generate dynamic OG images and store them locally. &lt;/p&gt;

&lt;p&gt;This was not my first approach to serve dynamic OG images. In my first approach, I had a serverless function that would take a screenshot of the page and returns the image in a Base64 string. I basically copied the idea from Wes Bos. You can watch the video &lt;a href="https://www.youtube.com/watch?v=A0Ww-SU7K5E"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/A0Ww-SU7K5E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It worked well for some time, but I was not happy with the performance. Sometimes the function took way too long to return the image and I also had some problems where the function was removed after deployment via a webhook on Netlify.&lt;/p&gt;

&lt;p&gt;This is why I searched for another solution. My next thought was to do the same logic but store the images at Cloudinary. As soon as I had finished it, I was thinking if I could skip the Cloudinary altogether and store the image locally. After some research, I discover I can just store the image inside the &lt;code&gt;./public&lt;/code&gt; folder and Next.js can find it without any problem. I love the idea of serving the OG images from the same Vercel CDN that the Next.js app is on. &lt;/p&gt;

&lt;p&gt;After writing this post I discovered that Vercel has built &lt;a href="https://og-image.vercel.app/"&gt;https://og-image.vercel.app/&lt;/a&gt;, which will solve the same problem, but differently. For now, I will continue using my custom brewed solution but I might check it out for a future project.&lt;/p&gt;




&lt;p&gt;If you liked this post, you might like some of my tweets too. Go follow me &lt;a href="https://twitter.com/phiilu"&gt;@phiilu&lt;/a&gt; on Twitter where I share things related to web development!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>seo</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>My thoughts on completing the React Fundamentals workshop from Epic React by Kent C. Dodds</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Mon, 12 Oct 2020 21:15:34 +0000</pubDate>
      <link>https://dev.to/phiilu/my-thoughts-on-completing-the-react-fundamentals-workshop-from-epic-react-by-kent-c-dodds-6oo</link>
      <guid>https://dev.to/phiilu/my-thoughts-on-completing-the-react-fundamentals-workshop-from-epic-react-by-kent-c-dodds-6oo</guid>
      <description>&lt;p&gt;I just finished my first workshop of the &lt;a href="https://epicreact.dev/"&gt;Epic React by Kent C. Dodds&lt;/a&gt; course. After each workshop, I thought I might write down my thoughts and summarize my learnings.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/4IVsPuDbTzkNTNML2kZK7u/6b4feed5ace8a873083528a9551c56a7/Screenshot_2020-10-12_at_21.26.08.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/4IVsPuDbTzkNTNML2kZK7u/6b4feed5ace8a873083528a9551c56a7/Screenshot_2020-10-12_at_21.26.08.png" alt="React Fundamentals Certificate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Epic React is a &lt;strong&gt;BIG&lt;/strong&gt; online course with a total of 8 different workshops and I don't know how many hours of videos, I just know that there are a lot! &lt;/p&gt;

&lt;p&gt;If you want to learn or — like in my case — get better using React than this is probably the best course money can buy. Kent is an amazing instructor and he explains things very understandable. This epic course is so different than other online courses. It's more like an actual workshop where you attend in person. It will make sure that you will get your hands dirty and not just watch the videos!&lt;/p&gt;

&lt;p&gt;The first module was React Fundamentals. It will teach you: How React works, what JSX is, and how to do simple things like creating components, add styling, and implement forms.  &lt;/p&gt;

&lt;h2&gt;
  
  
  How I approached this course
&lt;/h2&gt;

&lt;p&gt;Usually, when I buy an online course, I just watch the videos and maybe try out a few things on my own. Kent's workshops, however, are not intended to just watch them, &lt;strong&gt;YOU&lt;/strong&gt; will be doing most of the exercises and watch the solution afterward.&lt;/p&gt;

&lt;p&gt;With each workshop, you will get access to a repository hosted on GitHub. It has all the files that you will need to follow along.&lt;/p&gt;

&lt;p&gt;These are the steps I did for each section of the workshop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Watch the intro video where Kent introduces the topic/problem&lt;/li&gt;
&lt;li&gt;Open the corresponding file in the editor and browser&lt;/li&gt;
&lt;li&gt;In the browser read the whole description about the topic and what should be done&lt;/li&gt;
&lt;li&gt;Solving the exercise (+ extra credit) and if there is a test run it to verify it is working&lt;/li&gt;
&lt;li&gt;Go back to the video and watch how Kent solves it and listen to the tips he gives&lt;/li&gt;
&lt;li&gt;Complete the section and go to the next one&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I like this approach and it helps me to try things out and finally understand them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I learn?
&lt;/h2&gt;

&lt;p&gt;More than I expected! I think I am already pretty good at React, but as it turns out I have skipped some fundamentals. 😅 &lt;/p&gt;

&lt;p&gt;At the beginning of this workshop, Kent will explain how React works under the hood without JSX. I knew JSX is just syntactical sugar and React uses plain JavaScript methods under the hood, but I never had to use them. Learning how the function &lt;code&gt;React.createElement&lt;/code&gt; work will let you understand JSX much better. &lt;/p&gt;

&lt;p&gt;Another thing was writing custom &lt;a href="https://reactjs.org/docs/typechecking-with-proptypes.html"&gt;PropTypes&lt;/a&gt;. I have used PropTypes before but never wrote a custom one myself. It's good to know that this exists, but to be fair I think I won't be writing PropTypes a lot in the future. I think switching to TypeScript would be better than defining PropTypes on top of JavaScript. &lt;/p&gt;

&lt;p&gt;Finally, I now completely understand why React needs the &lt;code&gt;key&lt;/code&gt; prop when looping over an array and rendering it. The demo with the input focus made me realize how important it is to use something unique to the item and not generated dynamically like the index!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So far I like this course and I am very happy that I bought it and I can't wait to go through the next workshop which will be about hooks! &lt;/p&gt;

&lt;p&gt;If you are learning React or already use it at work don't hesitate to buy it. I know it is expensive, but compared to the value you will get for that money, it's not! In some countries, parity purchasing power is available too.&lt;/p&gt;

&lt;p&gt;Now back to learning and going through the React Hook workshop! Of course, I will give you an update on how the workshop was later on. &lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>This post has 8 reactions! - Using the dev.to API to update the post title with the reactions count!</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Sat, 10 Oct 2020 10:38:27 +0000</pubDate>
      <link>https://dev.to/phiilu/this-post-has-0-reactions-using-the-dev-to-api-to-update-the-post-title-with-the-number-of-reactions-4bfm</link>
      <guid>https://dev.to/phiilu/this-post-has-0-reactions-using-the-dev-to-api-to-update-the-post-title-with-the-number-of-reactions-4bfm</guid>
      <description>&lt;p&gt;This is just a silly experiment inspired by this video from Ben Awad where he reverse-engineered the TikTok API to update his profile with live stats of one of his TikTok videos.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RxkLFAGetVQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I wanted to try this out too and thought it might work on the dev.to platform too. I looked up the API documentation and found the endpoints that I need to use to make this work!&lt;/p&gt;

&lt;p&gt;I don't have any bad intentions and don't want to hit the API too often. Some of the API endpoints have rate limits too, therefore I am making sure that I only make requests when it is necessary and every 30 seconds. &lt;/p&gt;

&lt;p&gt;Here is the code that I used to make this work:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/phiilu" rel="noopener noreferrer"&gt;
        phiilu
      &lt;/a&gt; / &lt;a href="https://github.com/phiilu/dev.to-reactions-in-article-title"&gt;
        dev.to-reactions-in-article-title
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Using the dev.to API to update the article with the count of reactions.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;POST_ID&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="nx"&gt;POST_ID&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;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://dev.to/api`&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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="nx"&gt;API_KEY&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentReactionsCount&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;POST_ID&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/articles/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&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="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;updateArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;POST_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/articles/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;body&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sleep&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;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30000&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="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;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;while &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="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="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&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;public_reactions_count&lt;/span&gt; &lt;span class="p"&gt;}&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`This post has &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;public_reactions_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; reactions! - Using the dev.to API to update the post title with the reactions count!`&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;currentReactionsCount&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;public_reactions_count&lt;/span&gt;&lt;span class="p"&gt;)&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;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;POST_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newTitle&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="nx"&gt;currentReactionsCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;public_reactions_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Article updated - Reactions: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentReactionsCount&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Reaction count was the same`&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;error&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&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;p&gt;You can create a new API key inside your account settings and the API documentation can be found &lt;a href="https://docs.forem.com/api/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &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%2Fi%2Fbqwzy5arzunzhnhd0sb5.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%2Fi%2Fbqwzy5arzunzhnhd0sb5.png" alt="dev.to account settings"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now if this is working this post should get updated every 30 seconds with the current reaction count! &lt;/p&gt;




&lt;p&gt;Edit:&lt;/p&gt;

&lt;p&gt;I noticed that the API might not return up-to-date data and therefore takes maybe longer than 30 seconds.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Add Apple Maps to your website using a serverless function and host it on Netlify</title>
      <dc:creator>Florian Kapfenberger</dc:creator>
      <pubDate>Tue, 06 Oct 2020 16:37:59 +0000</pubDate>
      <link>https://dev.to/phiilu/add-apple-maps-to-your-website-using-a-serverless-function-and-host-it-on-netlify-e9e</link>
      <guid>https://dev.to/phiilu/add-apple-maps-to-your-website-using-a-serverless-function-and-host-it-on-netlify-e9e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;span&gt;Cover Photo by &lt;a href="https://unsplash.com/@arttravelling?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;oxana v&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/map?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I bet at one point in your developer career you were asked or wanted to implement an interactive map on your website. Interactive maps are great to visualize locations and routes for users and have replaced the need of using an old-school map helping us to navigate in the real world!&lt;/p&gt;

&lt;p&gt;So you are now on your way to implement such a feature and the first thing you will do is search on google &lt;strong&gt;"How do implement an interactive map on a website"&lt;/strong&gt; and probably the first few results will point you to use Google Maps. But what if I tell you that Google is not the only player in the game and there are different maps provider too!&lt;/p&gt;

&lt;p&gt;In this blog post, we will be focusing on &lt;a href="https://developer.apple.com/documentation/mapkitjs"&gt;Apple Maps&lt;/a&gt;! Yeah, you heard correctly Apple Maps. &lt;em&gt;"But doesn't Apple Maps suck? Apple Maps on a website? I thought that only exists on iOS?"&lt;/em&gt; you might ask and the answers are as usual "it depends" and "yeah it exists!". &lt;/p&gt;

&lt;p&gt;You know Apple has released &lt;strong&gt;Apple Mapkit for JS&lt;/strong&gt; in 2015 and since that year you can implement Apple Maps on websites! The most popular service I can find using Apple Maps is DuckDuckGo. If you search &lt;a href="https://duckduckgo.com/?q=where+is+vienna&amp;amp;t=ffab&amp;amp;atb=v233-1&amp;amp;ia=web"&gt;"where is vienna"&lt;/a&gt; you will find a small map on the right of the search results using Apple Maps. If you click it, you will get redirect to a &lt;a href="https://duckduckgo.com/?q=where+is+vienna&amp;amp;t=ffab&amp;amp;atb=v233-1&amp;amp;ia=web&amp;amp;iaxm=about&amp;amp;iax=images"&gt;site which has a bigger map&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/3Hmr5g4f31R4s7ZdmgsFvz/089d327af77c258008f31e9ad5691309/Screenshot_2020-10-04_at_19.08.33.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/3Hmr5g4f31R4s7ZdmgsFvz/089d327af77c258008f31e9ad5691309/Screenshot_2020-10-04_at_19.08.33.png" alt="DuckDuckGo Apple Maps search result" title="DuckDuckGo Apple Maps search result"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Okay now you know you could use Apple Maps on your website, but how do you make it work? Well, it is not as easy as it seems, because Apple handles the API key differently than Google Maps.&lt;/p&gt;

&lt;p&gt;Usually when you want to use a service you will sign up on their site and generate an API key that you than can use. Apple instaed will give you a private key which you can use to generate &lt;a href="https://jwt.io/"&gt;Json Web Token (JWT)&lt;/a&gt; tokens that will be used to validate your access to Apple Maps. This is why we need backend code to hide this private key and therefore we will implement this with a serverless function.  &lt;/p&gt;

&lt;p&gt;In this post, I will show you how to get the credentials from Apple, create a small example using Apple Maps + serverless function, and finally deploy it using Netlify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You will need to have a &lt;a href="https://developer.apple.com/support/compare-memberships/"&gt;paid Apple Developer membership&lt;/a&gt;, otherwise, you will not be able to download the credentials for using Apple Maps&lt;/li&gt;
&lt;li&gt;A free Netlify account to be able to deploy the final website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sadly you will need to be in Apple's Developer program if you want to follow along, if you can't follow along you may still check out the final version &lt;a href="https://apple-maps-example.netlify.app/"&gt;here&lt;/a&gt; and take a look at the code on &lt;a href="https://github.com/phiilu/apple-maps-example"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Obtaining the credentials from Apple
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I did not figure out these steps on my own and they can be found on &lt;a href="https://developer.apple.com/documentation/mapkitjs/creating_a_maps_identifier_and_a_private_key"&gt;Apple's website&lt;/a&gt; too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First head over to the &lt;a href="https://developer.apple.com/account/"&gt;account page&lt;/a&gt; of your Apple Developer membership. We need to create two things: Maps ID and a MapKit JS private key. &lt;/p&gt;

&lt;h3&gt;
  
  
  Maps ID
&lt;/h3&gt;

&lt;p&gt;In the sidebar select &lt;strong&gt;Certificates, Identifiers &amp;amp; Profiles&lt;/strong&gt; and than &lt;strong&gt;Identifiers&lt;/strong&gt; and click the small plus icon.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/77XDq9HO4IYlOAV9jPgHeU/7a0fff5c50164b381127d8c4d8754a06/Screenshot_2020-10-04_at_20.31.23.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/77XDq9HO4IYlOAV9jPgHeU/7a0fff5c50164b381127d8c4d8754a06/Screenshot_2020-10-04_at_20.31.23.png" alt="Apple Developer Console - Certificates, Identifiers &amp;amp; Profiles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/qFYPUkR6TewPh6L7LPRyg/c0240184f22d18b44a4e8725809e410f/Screenshot_2020-10-04_at_20.28.20.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/qFYPUkR6TewPh6L7LPRyg/c0240184f22d18b44a4e8725809e410f/Screenshot_2020-10-04_at_20.28.20.png" alt="Apple Developer Console - Create Identifier"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, give it a description and an identifier using a reverse domain like &lt;code&gt;maps.com.phiilu.example&lt;/code&gt; and click &lt;strong&gt;Continue&lt;/strong&gt; and &lt;strong&gt;Register&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/2o67Hb0LOoKNWxJuOfqOvv/0b20060f6d22fd901d16520c0cf3d880/Screenshot_2020-10-04_at_20.30.10.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/2o67Hb0LOoKNWxJuOfqOvv/0b20060f6d22fd901d16520c0cf3d880/Screenshot_2020-10-04_at_20.30.10.png" alt="Apple Developer Console - Create Maps ID"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have created a Maps ID which we will need for our Private Key. &lt;/p&gt;

&lt;h3&gt;
  
  
  MapKit JS private key
&lt;/h3&gt;

&lt;p&gt;Back on the &lt;strong&gt;Certificates, Identifiers &amp;amp; Profiles&lt;/strong&gt; overview page select &lt;strong&gt;Keys&lt;/strong&gt; and create a new key by selecting the small plus icon.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/5y0c0Lof8nVUDEFodsiTMn/a0dd4f4cba52ccff91db5c06ac9099c4/Screenshot_2020-10-04_at_20.43.26.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/5y0c0Lof8nVUDEFodsiTMn/a0dd4f4cba52ccff91db5c06ac9099c4/Screenshot_2020-10-04_at_20.43.26.png" alt="Apple Developer Console - Create Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give the Key a name and you should see MapKit JS as an option in the services. If not then you did not create a Maps ID. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/6DVZjAH2yqF6Wh7uRNEchf/01306d1697efbf5f81c98e2015ab81d1/Screenshot_2020-10-04_at_20.46.59.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/6DVZjAH2yqF6Wh7uRNEchf/01306d1697efbf5f81c98e2015ab81d1/Screenshot_2020-10-04_at_20.46.59.png" alt="Apple Developer Console - Create MapKit JS Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next select Configure next to the MapKit JS, select the previously created Maps ID, and click &lt;strong&gt;Save&lt;/strong&gt;. Review your settings and click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/T3AjkhZH24SoB1xKVva7p/e169e846f35cf840c831da605aa49372/Screenshot_2020-10-04_at_20.47.33.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/T3AjkhZH24SoB1xKVva7p/e169e846f35cf840c831da605aa49372/Screenshot_2020-10-04_at_20.47.33.png" alt="Apple Developer Console - Select Maps ID for MapKit JS Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see a summary of the services you enabled for this key and can register your key with &lt;strong&gt;Register&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/2T9vYkzvslLYdWhe1tHx0A/fa54024a28c455218a873c1b2e70bc48/Screenshot_2020-10-04_at_20.53.38.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/2T9vYkzvslLYdWhe1tHx0A/fa54024a28c455218a873c1b2e70bc48/Screenshot_2020-10-04_at_20.53.38.png" alt="Apple Developer Console - Key summary before registering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, your key is registered and you can download it! As the warning says this is a &lt;strong&gt;ONE TIME DOWNLOAD&lt;/strong&gt; and you won't be able to download the key again. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/6gmOtazTEYlkZxeeGkn6DF/1b53d603a1f66dbaa8a46c6f347d4490/Screenshot_2020-10-04_at_20.53.51.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/6gmOtazTEYlkZxeeGkn6DF/1b53d603a1f66dbaa8a46c6f347d4490/Screenshot_2020-10-04_at_20.53.51.png" alt="Apple Developer Console - Download Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will download a file named &lt;code&gt;AuthKey_Z11AA36DZ4.p8&lt;/code&gt; or similar and if you open this you will find 6 lines of text, which will be your private key. &lt;/p&gt;

&lt;p&gt;This key should &lt;strong&gt;ALWAYS&lt;/strong&gt; be kept private and never be leaked to the public, hence why we need to create a serverless function to safely store the key in backend code.&lt;/p&gt;

&lt;p&gt;Take note of the generated &lt;strong&gt;Key ID&lt;/strong&gt; too, because you will need it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team ID
&lt;/h3&gt;

&lt;p&gt;The Team ID is already created for you, but you need to copy it from the top right next to your name.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/3jNAjIAG5NEFJsN4USllts/68a3524c70b74ff04a57c688291b6d8a/Screenshot_2020-10-05_at_21.13.08.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/3jNAjIAG5NEFJsN4USllts/68a3524c70b74ff04a57c688291b6d8a/Screenshot_2020-10-05_at_21.13.08.png" alt="Apple Developer Console - Team ID"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now that we have everything we need and we can start integrating Apple Maps on our website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the development environment
&lt;/h2&gt;

&lt;p&gt;This project is using the &lt;a href="https://www.netlify.com/products/dev/"&gt;Netlify CLI&lt;/a&gt;, so you will need to install it globally using npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;netlify-cli &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, you will need to clone the git repository and checkout the branch &lt;code&gt;tutorial-start&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:phiilu/apple-maps-example.git
&lt;span class="nb"&gt;cd &lt;/span&gt;apple-maps-example &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git checkout tutorial-start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To start the project you need to install the dependencies and run the development server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you open the browser at &lt;code&gt;http://localhost:18080&lt;/code&gt; you will see a simple styled website where we will be adding Apple Maps.&lt;/p&gt;

&lt;p&gt;To keep it simple we will be using basic HTML, vanilla Javascript, and styles using &lt;a href="https://tailwindcss.com/"&gt;Tailwind&lt;/a&gt;. It will be compiled using &lt;a href="https://parceljs.org/"&gt;Parcel&lt;/a&gt; and the serverless functions can be tested locally using the Netlify CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project structure explained
&lt;/h2&gt;

&lt;p&gt;Inside the project directory run the following command to list all folders and files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;tree &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="s1"&gt;'dist|node_modules'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── netlify.toml
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
│   ├── functions
│   ├── index.html
│   ├── index.js
│   └── index.pcss
└── tailwind.config.js

2 directories, 9 files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Most of these files should not need explaining, but I will explain the most interesting ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  netlify.toml
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;netlify.toml&lt;/code&gt; contains the configuration for Netlify and the Netlify CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build]&lt;/span&gt;
  &lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npm run build"&lt;/span&gt;
  &lt;span class="py"&gt;functions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/functions"&lt;/span&gt;
  &lt;span class="py"&gt;NODE_ENV&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"12"&lt;/span&gt;
  &lt;span class="py"&gt;publish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/dist"&lt;/span&gt;

&lt;span class="nn"&gt;[dev]&lt;/span&gt;
  &lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18080&lt;/span&gt;
  &lt;span class="py"&gt;publish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/dist"&lt;/span&gt;
  &lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npm run dev"&lt;/span&gt;

&lt;span class="nn"&gt;[production]&lt;/span&gt;
  &lt;span class="py"&gt;publish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/dist"&lt;/span&gt;
  &lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npm run build"&lt;/span&gt;

&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/api/*"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.netlify/functions/:splat"&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It will tell Netlify where our serverless functions are, which Node.js version we want to use, which port we want the development server should run, how it can start our project in development mode, and redirects all requests from &lt;code&gt;/api/*&lt;/code&gt; to the correct Netlify function. &lt;/p&gt;

&lt;h3&gt;
  
  
  src directory
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;src&lt;/code&gt; will contain all of the code we write. It contains the &lt;code&gt;functions&lt;/code&gt; directory where we will be putting our serverless functions and our HTML, Javascript, and CSS files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tailwind
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A utility-first CSS framework for rapidly building custom designs. - &lt;a href="https://tailwindcss.com/"&gt;tailwindcss.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lots of the other files you see are to configure Tailwind. I just like to use Tailwind to add styling to my projects, but we won't be adding more styles, so you will not need to know how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Apple Maps to the website
&lt;/h2&gt;

&lt;p&gt;Let's start adding Apple Maps to the website! When you open the cloned repo and open up the &lt;code&gt;index.html&lt;/code&gt; file you will see that there is already Apple MapKit pulled in from Apple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- index.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This line will load the latest MapKit from Apple. You can find how Apple versions MapKit &lt;a href="https://developer.apple.com/documentation/mapkitjs/loading_the_latest_or_a_specific_version_of_mapkit_js"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Further down the file, you will find the &lt;code&gt;div&lt;/code&gt; where we want to mount Apple Maps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"apple-maps"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height: 800px"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full rounded-md shadow-lg"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;div&lt;/code&gt; has set a &lt;code&gt;height&lt;/code&gt; and some classes that will make it look a bit nicer. If you open up &lt;code&gt;http://localhost:18080/&lt;/code&gt; you should see a simple website with navigation, a search box, and a blank space with a shadow around.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/3sVqvzueRVJtiQqbbaFEQP/bb74717671dbb5affbc6eea9a39d3650/Screenshot_2020-10-05_at_20.29.21.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/3sVqvzueRVJtiQqbbaFEQP/bb74717671dbb5affbc6eea9a39d3650/Screenshot_2020-10-05_at_20.29.21.png" alt="Apple Maps Example starting point"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we want to add Apple Maps to this &lt;code&gt;div&lt;/code&gt;, so open up the &lt;code&gt;index.js&lt;/code&gt; file and paste this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mapkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;authorizationCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/token&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;then&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&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;done&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userLanguage&lt;/span&gt;&lt;span class="p"&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;center&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;mapkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Coordinate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;48.210033&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;16.363449&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Vienna&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&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;mapkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apple-maps&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;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cameraDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&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;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/2ffGKcmb4GaZPHCFmkeaO/5998932ac1ff1b4c9b289ade256adc02/Screenshot_2020-10-05_at_20.38.38.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/2ffGKcmb4GaZPHCFmkeaO/5998932ac1ff1b4c9b289ade256adc02/Screenshot_2020-10-05_at_20.38.38.png" alt="Apple Maps Example - Initialize Map failed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should see a Map showing up in your browser... well not really. If you have a look at the console you will see an error and a warning.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/WNDluyrrfgWmMWYcPOLZk/aec9a71120bbd0e2b9bb1f648e2f0875/Screenshot_2020-10-05_at_20.40.32.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/WNDluyrrfgWmMWYcPOLZk/aec9a71120bbd0e2b9bb1f648e2f0875/Screenshot_2020-10-05_at_20.40.32.png" alt="Apple MapKit JS - failed initialization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's debug our code and find out why it is not working. &lt;/p&gt;

&lt;p&gt;You can see, we are mounting the map correctly using the &lt;code&gt;id&lt;/code&gt; of &lt;code&gt;apple-maps&lt;/code&gt; that we specified in the &lt;code&gt;index.html&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;But before mounting we are calling &lt;code&gt;mapkit.init(...)&lt;/code&gt; and fetching a new token from &lt;code&gt;/api/token&lt;/code&gt;. This token is how Apple Maps verifies that this site has access to Apple Maps, but we did not write any code that would generate such a token yet. &lt;/p&gt;

&lt;p&gt;In the next section, we will write the serverless function that will generate our token for Apple Maps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a serverless function to create a valid JWT token for Apple Maps
&lt;/h2&gt;

&lt;p&gt;It's time to take our generated private key and use it to generate JWT tokens for our frontend!&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure .env with our obtained secrets
&lt;/h3&gt;

&lt;p&gt;Before working on the serverless function we will need to create a &lt;code&gt;.env&lt;/code&gt; file in the root of your project and put in our obtained credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MAPS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;MAP_KEY_ID&amp;gt;
&lt;span class="nv"&gt;APPLE_TEAM_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;TEAM_ID&amp;gt;
&lt;span class="nv"&gt;APPLE_MAPS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;PRIVATE_KEY&amp;gt;
&lt;span class="nv"&gt;SITE_ORIGIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:18080
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Put in your information that you saved from the previous sections. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;APPLE_MAPS_KEY&lt;/code&gt; is a little special, because we downloaded it as a file. You will need to copy the contents of the file and replace all line breaks with &lt;code&gt;\n&lt;/code&gt;. Your &lt;code&gt;APPLE_MAPS_KEY&lt;/code&gt; entry should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;APPLE_MAPS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;MIGTAgEAMBMAyyqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg698QaJcP/pbDrXHE&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Szt8Igy6KgeUm2ky1LZZoBDcVzygCgYI2oZIzj0DAQehRANCAAST56qF4NUi1v0Y&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;rWfvSNVbSdQjT7M6syC7bJYiB5zTlwzzGeIU2kilRNY7p3KlUnC5QGISGHjN3FL+&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;7wjrPBNa&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-----END PRIVATE KEY-----"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Don't worry, this is not my real private key&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your &lt;code&gt;.env&lt;/code&gt; file should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MAPS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AAAAAAB994
&lt;span class="nv"&gt;APPLE_TEAM_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1234A8E9Z5
&lt;span class="nv"&gt;APPLE_MAPS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;MIGTAgEAMBMAyyqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg698QaJcP/pbDrXHE&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Szt8Igy6KgeUm2ky1LZZoBDcVzagCgYI2oZIzj0DAQehRANCAAST56qF4NUi1v0Y&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;rWfvSNVbSdQjT7M6syC7bJYiB5zTlwzzGeIU2kilRNY7p3KlUnC5QGISGHjN3FL+&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;7wjrPBNa&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-----END PRIVATE KEY-----"&lt;/span&gt;
&lt;span class="nv"&gt;SITE_ORIGIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:18080
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing the serverless function
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;token&lt;/code&gt; folder inside the &lt;code&gt;functions&lt;/code&gt; folder and open it in your editor. Next scaffold a &lt;code&gt;package.json&lt;/code&gt; using &lt;code&gt;npm&lt;/code&gt; inside the &lt;code&gt;token&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As I already mention we need to generate JWT tokens, so we will be using the &lt;code&gt;jsonwebtoken&lt;/code&gt; &lt;a href="https://github.com/auth0/node-jsonwebtoken"&gt;package&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i jsonwebtoken
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now create a file named &lt;code&gt;token.js&lt;/code&gt; and paste this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;jsonwebtoken&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="nx"&gt;origin&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="nx"&gt;SITE_ORIGIN&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;privatekey&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="nx"&gt;APPLE_MAPS_KEY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;n/gm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="nx"&gt;keyid&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="nx"&gt;MAPS_KEY_ID&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;issuer&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="nx"&gt;APPLE_TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;origin&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;privatekey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ES256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;keyid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;issuer&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&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;p&gt;This simple function will generate a JWT token that is signed using your private key. The private key needs to replace the &lt;code&gt;\\n&lt;/code&gt; with real &lt;code&gt;\n&lt;/code&gt;, because Netlify somehow adds this automatically. I found the solution through &lt;a href="https://github.com/auth0/node-jsonwebtoken/issues/642#issuecomment-585173594"&gt;this comment&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;The token will be valid for &lt;code&gt;1 day&lt;/code&gt; and will only work on the website configured in &lt;code&gt;SITE_ORIGIN&lt;/code&gt;. You can change these options if you want and adjust them to your needs.&lt;/p&gt;

&lt;p&gt;Apple has documented what each field is and what is used for at their &lt;a href="https://developer.apple.com/documentation/mapkitjs/creating_and_using_tokens_with_mapkit_js"&gt;documentation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Restart your development server and open up &lt;code&gt;http://localhost:18080/&lt;/code&gt; and you should see a map with the center being Vienna 🥳&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/3dPfSCJ0C0kvQbP55NDcWq/d072cc0ca95c7bb9be9bafccc248d71f/Screenshot_2020-10-05_at_21.39.01.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/3dPfSCJ0C0kvQbP55NDcWq/d072cc0ca95c7bb9be9bafccc248d71f/Screenshot_2020-10-05_at_21.39.01.png" alt="Apple Maps Example - the map is working"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the website to Netlify
&lt;/h2&gt;

&lt;p&gt;Now that our Map is working we want to publish our little project on Netlify and we will do this using the Netlify CLI that you already know.&lt;/p&gt;

&lt;p&gt;If you did not login to Netlify using the CLI you will have to do this first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;netlify login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, go to the root of your project directory and run &lt;code&gt;netlify deploy&lt;/code&gt;. This will walk you through and create the site on Netlify.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;netlify deploy
This folder isn't linked to a site yet
? What would you like to do? +  Create &amp;amp; configure a new site
? Team: Florian Kapfenberger's team
Choose a unique site name (e.g. isnt-phiilu-awesome.netlify.app) or leave it blank for a random name. You can update the site name later.
? Site name (optional): apple-maps-example

Site Created

Admin URL: https://app.netlify.com/sites/apple-maps-example
URL:       https://apple-maps-example.netlify.app
Site ID:   44aa1f3e-f887-47e0-bf55-4c80f2ec01ab
Deploy path:        /Users/florian/Code/privat/projects/apple-maps-example/dist
Functions path:     /Users/florian/Code/privat/projects/apple-maps-example/src/functions
Configuration path: /Users/florian/Code/privat/projects/apple-maps-example/netlify.toml
Deploying to draft URL...
✔ Finished hashing 10 files and 1 functions
✔ CDN requesting 9 files and 1 functions
 ›   Warning:
 ›   {}
 ›
    TypeError: Cannot read property '0' of undefined
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see I got an error the first time, so I reran the script and it worked!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;netlify deploy
Deploy path:        /Users/florian/Code/privat/projects/apple-maps-example/dist
Functions path:     /Users/florian/Code/privat/projects/apple-maps-example/src/functions
Configuration path: /Users/florian/Code/privat/projects/apple-maps-example/netlify.toml
Deploying to draft URL...
✔ Finished hashing 10 files and 1 functions
✔ CDN requesting 9 files and 1 functions
✔ Finished uploading 10 assets
✔ Deploy is live!

Logs:              https://app.netlify.com/sites/apple-maps-example/deploys/5f7b795ed6d83e3ab0a3f4a2
Website Draft URL: https://5f7b795ed6d83e3ab0a3f4a2--apple-maps-example.netlify.app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our site may be deployed, but it is still not working, because we need to configure the environment variables in Netlify first.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;netlify.com&lt;/code&gt;, log in and go to the &lt;strong&gt;Build &amp;amp; deploy&lt;/strong&gt; settings of the created project and add the environment variables from your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/hb3id6ag4raq/6UZH0ssIWmntSF43mocxv0/e6124f2a45c250c8e86978ec5aee797c/Screenshot_2020-10-05_at_22.30.37.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/hb3id6ag4raq/6UZH0ssIWmntSF43mocxv0/e6124f2a45c250c8e86978ec5aee797c/Screenshot_2020-10-05_at_22.30.37.png" alt="Netlify Dashboard - add environment variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is to deploy your project again, but this time with the &lt;code&gt;--prod&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;netlify deploy &lt;span class="nt"&gt;--prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your project is now live and you can visit it at the URL the Netlify CLI displays you or you can check out mine at &lt;a href="https://apple-maps-example.netlify.app/"&gt;https://apple-maps-example.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a Searchbox using Apple Maps (optional)
&lt;/h2&gt;

&lt;p&gt;For now, we just displayed a map but did not interact with it. In this section, I want to show you how you can easily search for cities and transition to them using just a few lines of code.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;index.js&lt;/code&gt; and append the following lines to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&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;mapkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userLanguage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;getsUserLocation&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="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&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;searchFormElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search-form&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="nx"&gt;searchBoxElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search-box&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;searchFormElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&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;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchBoxElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setRegionAnimated&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="nx"&gt;boundingRegion&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;p&gt;Using &lt;code&gt;new mapkit.Search(...)&lt;/code&gt; we can create an instance of &lt;code&gt;Search&lt;/code&gt; and use it to search for locations on the map.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;document.getElementById(...)&lt;/code&gt; lines will be used to get references to the &lt;code&gt;form&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; elements of the page.&lt;/p&gt;

&lt;p&gt;After we have the reference we can add an event listener for the &lt;code&gt;submit&lt;/code&gt; event on the &lt;code&gt;form&lt;/code&gt;. We prevent the default even, which would reload the page and get the text from the &lt;code&gt;searchBoxElement&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Next, we perform the search using &lt;code&gt;search.search(...)&lt;/code&gt;. The first argument is the query and the second one will be a callback when the request has finished. If we don't have an error we will use &lt;code&gt;map.setRegionAnimated(data.boundingRegion)&lt;/code&gt; to transition to the found location on the map.&lt;/p&gt;

&lt;p&gt;Try it out! Open up &lt;code&gt;http://localhost:18080/&lt;/code&gt; or &lt;a href="https://apple-maps-example.netlify.app/"&gt;https://apple-maps-example.netlify.app/&lt;/a&gt; and search for a city like Berlin and press enter. You will see the map transition to the center of Berlin.&lt;/p&gt;

&lt;p&gt;If it works, you can deploy your changes with &lt;code&gt;netlify deploy --prod&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>serverless</category>
      <category>javascript</category>
      <category>applemaps</category>
    </item>
  </channel>
</rss>
