<?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: Vincent</title>
    <description>The latest articles on DEV Community by Vincent (@vvo).</description>
    <link>https://dev.to/vvo</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%2F24267%2F5a01443f-1a29-4e00-8673-1081deacee87.png</url>
      <title>DEV Community: Vincent</title>
      <link>https://dev.to/vvo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vvo"/>
    <language>en</language>
    <item>
      <title>How to fix Spotlight using hundreds of GBs on your mac</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Wed, 14 May 2025 08:26:15 +0000</pubDate>
      <link>https://dev.to/vvo/how-to-avoid-spotlight-using-hundreds-of-gbs-and-rebuild-its-index-4kki</link>
      <guid>https://dev.to/vvo/how-to-avoid-spotlight-using-hundreds-of-gbs-and-rebuild-its-index-4kki</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr; To solve Spotlight using hundreds of Gbs, boot in recovery mode and delete its folder: &lt;code&gt;rm -rf /System/Volumes/Data/.Spotlight-V100&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://support.apple.com/guide/mac-help/prevent-spotlight-searches-in-files-mchl1bb43b84/mac" rel="noopener noreferrer"&gt;configure Spotlight to ignore&lt;/a&gt; folders containing millions of non-search-relevant files.&lt;/p&gt;




&lt;p&gt;A few days ago my computer started to act VERY weirdly. I have an Apple M3 Pro running macOS Sequoia. Everything started to slow down and I could not figure out why.&lt;/p&gt;

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

&lt;p&gt;Looking at the &lt;a href="https://support.apple.com/en-vn/guide/activity-monitor/welcome/mac" rel="noopener noreferrer"&gt;Activity Monitor&lt;/a&gt; I saw of course mentions of &lt;code&gt;mds&lt;/code&gt; and &lt;code&gt;mds_stores&lt;/code&gt;. I immediately blamed Spotlight which has been  a big CPU consumer since it was ever created.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;the issue was not the CPU usage&lt;/strong&gt;, my computer was actually having a hard time writing to disk.&lt;/p&gt;

&lt;p&gt;Looking at tools like &lt;a href="https://daisydiskapp.com/" rel="noopener noreferrer"&gt;DaisyDisk&lt;/a&gt; I discovered that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My disk was almost full (1TB!)&lt;/li&gt;
&lt;li&gt;Most of the used space (500GB) was in the "hidden space"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DaisyDisk &lt;a href="https://daisydiskapp.com/guide/2/en/HiddenSpace/#:~:text=(hidden%20space)%20is%20a%20virtual,our%20troubleshooting%20guide%20for%20details." rel="noopener noreferrer"&gt;help page&lt;/a&gt; says "Large amounts of hidden disk space may also indicate to file system errors. Follow our troubleshooting guide for details."&lt;/p&gt;

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

&lt;p&gt;And it turns out, my Storage settings in macOS was also saying I had huge amounts of "System data" being used:&lt;/p&gt;

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

&lt;p&gt;After multiple hours of searching around how to solve this, including commands like &lt;code&gt;sudo mdutil -E&lt;/code&gt;, &lt;code&gt;sudo mdutil -X&lt;/code&gt;, and &lt;strong&gt;disk repair&lt;/strong&gt; attempts ... I finally found the exact issue.&lt;/p&gt;

&lt;p&gt;Before trying out any other solution, if you have huge amounts of system data showing up as used in your storage system settings then follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/guide/mac-help/use-macos-recovery-on-an-intel-based-mac-mchl338cf9a8/mac" rel="noopener noreferrer"&gt;Boot in recovery mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open the terminal&lt;/li&gt;
&lt;li&gt;Remove the Spotlight folder &lt;code&gt;rm -rf /System/Volumes/Data/.Spotlight-V100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Boot in normal mode&lt;/li&gt;
&lt;li&gt;See that you recovered your space!&lt;/li&gt;
&lt;li&gt;Now make sure to &lt;a href="https://support.apple.com/guide/mac-help/prevent-spotlight-searches-in-files-mchl1bb43b84/mac" rel="noopener noreferrer"&gt;ignore any folder&lt;/a&gt; containing a lot of non-relevant files. For example avoid &lt;code&gt;Dev/&lt;/code&gt; style folders with &lt;code&gt;node_modules&lt;/code&gt; in them, or any temp/ logs/ folders that have millions of files in them&lt;/li&gt;
&lt;li&gt;Activate Spotlight indexing: &lt;code&gt;sudo mdutil -E /&lt;/code&gt; (it should already be on but whatever)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bonus tip: how to show folder sizes in finder
&lt;/h2&gt;

&lt;p&gt;Before I understood this was a Spotlight issue (always bet on Spotlight for cpu or space issues first), I was trying to find what are the biggest folders on my computer.&lt;/p&gt;

&lt;p&gt;To solve this, there's a very nice way to always display the total sizes of folders on screen in &lt;a href="https://support.apple.com/guide/mac-help/organize-your-files-in-the-finder-mchlp2605/mac" rel="noopener noreferrer"&gt;finder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In any finder window, use ⌘ + J then check "Calculate all sizes" and click on "Use as Defaults". That's it, now you can easily navigate and spot the biggest space consumers.&lt;/p&gt;

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

&lt;p&gt;If this has been useful, drop me a comment. See ya!&lt;/p&gt;

</description>
      <category>macos</category>
      <category>spotlight</category>
      <category>disk</category>
      <category>tip</category>
    </item>
    <item>
      <title>How to add Firebase service account json files to Vercel</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Tue, 24 Aug 2021 12:19:47 +0000</pubDate>
      <link>https://dev.to/vvo/how-to-add-firebase-service-account-json-files-to-vercel-ph5</link>
      <guid>https://dev.to/vvo/how-to-add-firebase-service-account-json-files-to-vercel-ph5</guid>
      <description>&lt;p&gt;If you're using Firebase, Next.js, Vercel, firebase-admin, and if you've been Googling for the past hour on how to load your Google service account JSON file the easiest way possible, then you've come to the right place.&lt;/p&gt;

&lt;p&gt;This blog post explains how to store the service account json file in a single environment variable and then pass it down to the Firebase admin SDK.&lt;/p&gt;

&lt;p&gt;Here's how to do it:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Download the service account JSON file
&lt;/h2&gt;

&lt;p&gt;Follow the steps at &lt;a href="https://firebase.google.com/docs/admin/setup" rel="noopener noreferrer"&gt;https://firebase.google.com/docs/admin/setup&lt;/a&gt; and download the JSON file.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Remove line breaks on the JSON file
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://www.textfixer.com/tools/remove-line-breaks.php" rel="noopener noreferrer"&gt;https://www.textfixer.com/tools/remove-line-breaks.php&lt;/a&gt; and paste the content of the JSON file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not&lt;/strong&gt; remove the &lt;code&gt;\n&lt;/code&gt; characters from the &lt;code&gt;private_key&lt;/code&gt; fields or the key won't be valid afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create a new Vercel environment variable with the JSON content
&lt;/h2&gt;

&lt;p&gt;Either via a command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vercel &lt;span class="nb"&gt;env &lt;/span&gt;add FIREBASE_SERVICE_ACCOUNT_KEY
? What’s the value of FIREBASE_SERVICE_ACCOUNT_KEY?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(paste the JSON content and submit)&lt;/p&gt;

&lt;p&gt;Or via the Vercel web ui: &lt;a href="https://vercel.com/docs/environment-variables" rel="noopener noreferrer"&gt;https://vercel.com/docs/environment-variables&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In both cases, the value will be encrypted.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use it locally in .env.local
&lt;/h2&gt;

&lt;p&gt;This is the tricky part, because you have to use single quotes instead of double quotes when surrounding the environment variable value in your file.&lt;/p&gt;

&lt;p&gt;I am using the gitignored &lt;code&gt;.env.local&lt;/code&gt; as explained in the Next.js documentation: &lt;a href="https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables" rel="noopener noreferrer"&gt;https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local
&lt;/span&gt;&lt;span class="py"&gt;FIREBASE_SERVICE_ACCOUNT_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'JSON file content without line breaks'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Use the environment variable
&lt;/h2&gt;

&lt;p&gt;Here's an example Next.js api route that will receive a Firebase id token and verify it. See &lt;a href="https://firebase.google.com/docs/auth/admin/verify-id-tokens#web" rel="noopener noreferrer"&gt;https://firebase.google.com/docs/auth/admin/verify-id-tokens#web&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/user.ts&lt;/span&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;admin&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;firebase-admin&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&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;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;serviceAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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;FIREBASE_SERVICE_ACCOUNT_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&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="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&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;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceAccount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;databaseURL&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;NEXT_PUBLIC_FIREBASE_DATABASE_URL&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="c1"&gt;// example usage &lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;revenueCatApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;firebaseToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;split&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="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;firebaseToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="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;decodedToken&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;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;verifyIdToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebaseToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decodedToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&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;That's it! Hope this helps.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I made an app: Discover how people react to you on GitHub</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Mon, 08 Mar 2021 23:13:45 +0000</pubDate>
      <link>https://dev.to/vvo/i-made-an-app-discover-how-people-react-to-you-on-github-1j5c</link>
      <guid>https://dev.to/vvo/i-made-an-app-discover-how-people-react-to-you-on-github-1j5c</guid>
      <description>&lt;p&gt;Hey there dev community, I always wanted to build this: an app that lists and summarizes all the reactions I received on my GitHub comments.&lt;/p&gt;

&lt;p&gt;It took me some time (one week) but I made it! You can access it here: &lt;a href="https://sourcekarma.vercel.app" rel="noopener noreferrer"&gt;https://sourcekarma.vercel.app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can share your score on Twitter and also get a nice badge for your GitHub readme:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/vvo" rel="noopener noreferrer"&gt;
        vvo
      &lt;/a&gt; / &lt;a href="https://github.com/vvo/vvo" rel="noopener noreferrer"&gt;
        vvo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Hi there 👋&lt;/h1&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Maker&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://turnshift.app/" rel="nofollow noopener noreferrer"&gt;TurnShift&lt;/a&gt;: Automate Your Team Schedule&lt;/li&gt;
&lt;li&gt;Tweets &lt;a href="https://twitter.com/vvoyer" rel="nofollow noopener noreferrer"&gt;@vvoyer&lt;/a&gt;: JavaScript, React, Next.js, Building In Public&lt;/li&gt;
&lt;li&gt;Next.js News: The Next.js Monthly Newsletter (sold)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://sourcekarma.vercel.app/" rel="nofollow noopener noreferrer"&gt;Source Karma&lt;/a&gt;: Discover how people react to you on GitHub 👍 (archived)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vvo/vvo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The technical and design part were super fun, I am using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vercel&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;Puppeteer (to generate social images and GitHub badges)&lt;/li&gt;
&lt;li&gt;AWS RDS (PostgreSQL)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://craftwork.design/downloads/category/illustrations/?ref=87&amp;amp;campaign=Devto" rel="noopener noreferrer"&gt;Craftwork&lt;/a&gt; (for the awesome illustrations)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://usefathom.com/ref/Y8XVBV" rel="noopener noreferrer"&gt;Fathom&lt;/a&gt; (for stats)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give it a try and please ask any questions you have here!&lt;/p&gt;

&lt;p&gt;Best,&lt;br&gt;
Vincent&lt;/p&gt;

&lt;p&gt;PS: Here's the launch tweet&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1368953460947828746-517" src="https://platform.twitter.com/embed/Tweet.html?id=1368953460947828746"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1368953460947828746-517');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1368953460947828746&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to verify signatures from Slack incoming requests with Next.js</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Thu, 08 Oct 2020 13:54:00 +0000</pubDate>
      <link>https://dev.to/vvo/how-to-verify-signatures-from-slack-incoming-requests-with-next-js-25n7</link>
      <guid>https://dev.to/vvo/how-to-verify-signatures-from-slack-incoming-requests-with-next-js-25n7</guid>
      <description>&lt;p&gt;Hi there, this is a short blog post for anyone looking to handle Slack incoming requests with Next.js.&lt;/p&gt;

&lt;p&gt;When creating a Slack application that will interact with your web application, Slack will call you back on some actions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.slack.com/events-api" rel="noopener noreferrer"&gt;events API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;any &lt;a href="https://api.slack.com/interactivity" rel="noopener noreferrer"&gt;interactivity action&lt;/a&gt; like a button click in a message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example Slack &lt;a href="https://api.slack.com/methods/chat.postMessage" rel="noopener noreferrer"&gt;&lt;code&gt;postMessage&lt;/code&gt;&lt;/a&gt; call:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebClient&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;@slack/web-api&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;web&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebClient&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="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#general&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// fallback for mobile and email notifications:&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;Please visit &amp;lt;https://google.com&amp;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;blocks&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;section&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="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="s2"&gt;mrkdwn&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;Please visit https://google.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="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="s2"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;button&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="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="s2"&gt;plain_text&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;google&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="s2"&gt;`https://google.com`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go_to_google_button&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;If you do this, you'll get this result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqacxggthtnuk8y266moa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqacxggthtnuk8y266moa.png" alt="Screenshot of a Slack message with a button" width="787" height="259"&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you click on the "google" button, it will open Google in your browser. Pretty cool way to link from Slack to your web app for example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;, that's not the end of the story. If you only do this then Slack will complain because a &lt;a href="https://api.slack.com/reference/block-kit/block-elements#button" rel="noopener noreferrer"&gt;button&lt;/a&gt;, even if it only open a new url, is still an interactive element from Slack point of view.&lt;/p&gt;

&lt;p&gt;So Slack will try to call the URL defined in your application "Interactivity &amp;amp; Shortcuts" Slack settings.&lt;/p&gt;

&lt;p&gt;And it will fail because you've not defined a route for that.&lt;/p&gt;

&lt;p&gt;Now to solve that, if you're using Next.js, you have to define a route as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/slackInteractiveEndpoint.js&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;createMessageAdapter&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;@slack/interactive-messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// you can find this secret in your app "Basic information" tab&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slackInteractions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMessageAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slackSigningSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="nx"&gt;slackInteractions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&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="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;actionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go_to_google_button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// no need to reply, it's just a button to a url&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="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;slackInteractions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestListener&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bodyParser&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;externalResolver&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this route in your Slack application "Interactivity &amp;amp; Shortcuts" tab. Test one message, click on the button and .. &lt;em&gt;voilà!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because the Slack SDK is responsible for parsing the body and replying to the request, we tell Next.js to let it go via:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bodyParser: false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;externalResolver: true&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope this helped you! Post a comment otherwise :)&lt;/p&gt;

</description>
      <category>slack</category>
      <category>api</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Self-hosted analytics with umami on Vercel</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Thu, 20 Aug 2020 10:44:36 +0000</pubDate>
      <link>https://dev.to/vvo/self-hosted-analytics-with-umami-on-vercel-55ma</link>
      <guid>https://dev.to/vvo/self-hosted-analytics-with-umami-on-vercel-55ma</guid>
      <description>&lt;p&gt;&lt;strong&gt;EDIT Sep 4, 2020&lt;/strong&gt; After having stability issues with self-hosted Umami. I decided to use &lt;a href="https://usefathom.com/ref/Y8XVBV" rel="noopener noreferrer"&gt;Fathom Analytics&lt;/a&gt; and have been happy about it since then. I especially like the automated weekly email report on all my websites. &lt;strong&gt;Get $10 off&lt;/strong&gt; by using my link: &lt;a href="https://usefathom.com/ref/Y8XVBV" rel="noopener noreferrer"&gt;https://usefathom.com/ref/Y8XVBV&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://umami.is/" rel="noopener noreferrer"&gt;Umami&lt;/a&gt; is a self-hosted open-source alternative to Google Analytics. It provides simple and fast website analytics for your projects. It is built with Next.js and can run on MySQL or PostgreSQL. I believe it's a recent project and you can read more on the associated Hacker News discussion: &lt;a href="https://news.ycombinator.com/item?id=24198329" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=24198329&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how it looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvv138ivdw2o1z3ep2xlu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvv138ivdw2o1z3ep2xlu.png" alt="Screenshot of an example umami dashboard" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;
You can even share public pages of your analytics!



&lt;p&gt;I just started releasing products like &lt;a href="https://nextjsnews.com/" rel="noopener noreferrer"&gt;Next.js News&lt;/a&gt; and wanted to have some information on the people visiting my website.&lt;/p&gt;

&lt;p&gt;So far the products I knew about were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://usefathom.com/" rel="noopener noreferrer"&gt;Fathom&lt;/a&gt;: paid or self-hosted, in go&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://plausible.io/" rel="noopener noreferrer"&gt;Plausible Analytics&lt;/a&gt;: paid or self-hosted, in Elixir&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simpleanalytics.com/" rel="noopener noreferrer"&gt;Simple Analytics&lt;/a&gt;: paid&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://analytics.google.com" rel="noopener noreferrer"&gt;Google Analytics&lt;/a&gt;: you need a PHD to use it&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ackee.electerious.com/" rel="noopener noreferrer"&gt;Ackee&lt;/a&gt;: self-hosted&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://66analytics.com/" rel="noopener noreferrer"&gt;66Analytics&lt;/a&gt;: paid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am ready to pay for services, but the idea of having a self-hosted analytics service that was good enough was interesting. So let's see how to install umami on &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Create a database
&lt;/h1&gt;

&lt;p&gt;For Umami to work, you need a database. I went for the latest version of PostgreSQL on an &lt;a href="https://aws.amazon.com/rds/instance-types/#General_Purpose" rel="noopener noreferrer"&gt;AWS t2.micro&lt;/a&gt; which fits their free tier. But use whatever you want.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Create the necessary database tables
&lt;/h1&gt;

&lt;p&gt;Use the provided SQL dumps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema.postgresql.sql&lt;/li&gt;
&lt;li&gt;or schema.mysql.sql&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find them here: &lt;a href="https://github.com/mikecao/umami/tree/master/sql" rel="noopener noreferrer"&gt;https://github.com/mikecao/umami/tree/master/sql&lt;/a&gt;. It will also create the first user.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://tableplus.com/" rel="noopener noreferrer"&gt;https://tableplus.com/&lt;/a&gt; to connect to my local or production databases and execute queries on them.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Create a new project on Vercel
&lt;/h1&gt;

&lt;p&gt;Directly import the project from &lt;a href="https://github.com/mikecao/umami" rel="noopener noreferrer"&gt;https://github.com/mikecao/umami&lt;/a&gt;, Vercel will fork it on your GitHub account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frsjhwi6z1v25i72mzte8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frsjhwi6z1v25i72mzte8.png" alt="Screenshot of Vercel's new project window" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Configure the Vercel project
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Change the build command for&lt;/strong&gt;: &lt;code&gt;npm run build-postgresql-client &amp;amp;&amp;amp; npm run build&lt;/code&gt; if you're using PostgreSQL. Or &lt;code&gt;npm run build-mysql-client &amp;amp;&amp;amp; npm run build&lt;/code&gt; if you're using MySQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add two environment variables&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HASH_SALT&lt;/code&gt;, used to encrypt passwords in umami database. It should contain a random string. You can generate good ones with &lt;a href="https://1password.com/password-generator/" rel="noopener noreferrer"&gt;1password's strong password generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DATABASE_URL&lt;/code&gt;, It's the full url to access your database. It should look like this: postgresql://dbuser:dbPassword@dbHost[:dbPort]/dbName&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is all you need, for details you can have a look at &lt;a href="https://umami.is/docs/install" rel="noopener noreferrer"&gt;umami's install documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you can click on deploy!&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Login to the umami UI
&lt;/h1&gt;

&lt;p&gt;Go to your newly created Vercel application and login with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user: admin&lt;/li&gt;
&lt;li&gt;password: umami&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Change your password immediately.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Add websites
&lt;/h1&gt;

&lt;p&gt;Now you can add some websites, get the tracking code, add it to your code annnnd DONE. You now have a fully functional self-hosted analytics that looks good and is sufficient for a start.&lt;/p&gt;

&lt;p&gt;PS: Since what is deployed is a fork, you will have to update your fork to benefit from umami new features. See GitHub documentation on &lt;a href="https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork" rel="noopener noreferrer"&gt;how to sync a fork&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, follow me on twitter at &lt;a href="https://twitter.com/vvoyer" rel="noopener noreferrer"&gt;@vvoyer&lt;/a&gt; and share the article for your followers.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>umami</category>
      <category>nextjs</category>
      <category>aws</category>
    </item>
    <item>
      <title>Coding the Jamstack missing parts: databases, crons &amp; background jobs</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Wed, 19 Aug 2020 13:24:30 +0000</pubDate>
      <link>https://dev.to/vvo/coding-the-jamstack-missing-parts-databases-crons-background-jobs-1bpj</link>
      <guid>https://dev.to/vvo/coding-the-jamstack-missing-parts-databases-crons-background-jobs-1bpj</guid>
      <description>&lt;p&gt;&lt;em&gt;Summary:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For my current product, I use Next.js deployed on Vercel (If you're using Netlify, this article is also for you). And I use AWS for the other services I need: databases, crons, and background jobs.&lt;/p&gt;

&lt;p&gt;The AWS parts are deployed with &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS Cloud Development Kit&lt;/a&gt; (CDK). An &lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code" rel="noopener noreferrer"&gt;Infrastructure as Code&lt;/a&gt; tool allowing you (for example) to create PostgreSQL databases on AWS, using JavaScript files.&lt;/p&gt;

&lt;p&gt;I talk about AWS in this article, and it might be disturbing for readers: Because of its apparent complexity, you may not think of AWS as a solution for your Jamstack services needs. I thought the same. Then I discovered how easy it was to deploy AWS services using CDK and jumped right in.&lt;/p&gt;

&lt;p&gt;From reading issues and forums, I think we share a common struggle: &lt;strong&gt;how do you couple a Jamstack application with the essential infrastructure you need (databases, crons &amp;amp; background jobs) to create great products. This article is here to answer that.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Bonus: At the end of this article I provide you a takeaway example you can deploy on AWS and $5,000 credits.&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Discovering the Jamstack
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffqluztkmth9td9e5jhk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffqluztkmth9td9e5jhk7.png" alt="Jamstack definition from https://jamstack.wtf/" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;a href="https://jamstack.wtf/" rel="noopener noreferrer"&gt;jamstack.wtf&lt;/a&gt;



&lt;p&gt;As soon as I started to use platforms like &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; and &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, I thought: what about databases, crons, background jobs...? Jamstack platforms are providers allowing you to easily deploy static websites and API calls (through "lambda functions"), but that's it. &lt;strong&gt;If you need more than that, you need to use other services.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Coming from &lt;a href="http://heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;, I was disappointed because it meant I had to spend time researching other solutions, comparing them, and deciding if I should use it or not.&lt;/p&gt;

&lt;p&gt;And on top of that, it would possibly mean having lots of different moving parts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why do we even need crons and background jobs?
&lt;/h1&gt;

&lt;p&gt;At some point you need a database, that's obvious. As for crons and background jobs:&lt;/p&gt;

&lt;h2&gt;
  
  
  Crons
&lt;/h2&gt;

&lt;p&gt;Let's say you're building a product that monitors Twitter for specific words. For this to work, static generators and API routes won't be enough: you need something that can frequently call your API that in turn will check Twitter for such words. &lt;strong&gt;This is a cron job&lt;/strong&gt;. You cannot rely on user actions to achieve such work.&lt;/p&gt;

&lt;p&gt;Sure you could use &lt;a href="https://www.easycron.com/" rel="noopener noreferrer"&gt;EasyCron&lt;/a&gt;. But this would be another service to configure and pay for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background jobs
&lt;/h2&gt;

&lt;p&gt;Now in your UI, you also have a button that will generate a PDF report for specific words and send it to an email address. This is potentially a "long" running job, something like 10 seconds. The right way to handle this is to &lt;strong&gt;use background jobs&lt;/strong&gt; that will make sure this job is processed without your user having to wait in your UI for it to be done.&lt;/p&gt;

&lt;p&gt;One could say that this could be handled by just having an API route that replies immediately and continue to execute some code. Unfortunately, &lt;strong&gt;1.&lt;/strong&gt; This won't work on AWS (Vercel and Netlify are using AWS and &lt;a href="https://vercel.com/docs/platform/limits#streaming-responses" rel="noopener noreferrer"&gt;they document this limitation&lt;/a&gt;) and &lt;strong&gt;2.&lt;/strong&gt; If the job fails, you'd have to record it and inform the user they need to click again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jamstack platforms like Vercel and Netlify won't help you (for now) whenever you need some code to be executed on behalf of users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once I knew that and what I needed, I went solutions-hunting.&lt;/p&gt;

&lt;h1&gt;
  
  
  Requirements
&lt;/h1&gt;

&lt;p&gt;The solution I am looking for, should, for the most parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be a single solution, I don't want to maintain a job queue hosted on x.com and cron jobs hosted on y.com. This would be a mess. This is the case on Heroku where they have in-house solutions (databases) but also third-party solution providers (queues) with different UIs.&lt;/li&gt;
&lt;li&gt;Be cheap, I am cheap&lt;/li&gt;
&lt;li&gt;Be on AWS, because I got credits. Plus, most of Vercel's infrastructure is &lt;a href="https://vercel.com/docs/edge-network/regions" rel="noopener noreferrer"&gt;hosted on AWS&lt;/a&gt; (Same for Netlify). So I thought the network between my Vercel API routes and my database would be good.&lt;/li&gt;
&lt;li&gt;Be easy to administrate, modify, and deploy. Ideally with code, not YAML files. And without requiring me to create different projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh375gk0rtzr95yzzldrh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh375gk0rtzr95yzzldrh.png" alt="Google search example" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;
Google to the rescue



&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;p&gt;After multiple searches, I settled on using AWS this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database: &lt;a href="https://aws.amazon.com/fr/rds/" rel="noopener noreferrer"&gt;RDS&lt;/a&gt; with latest &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; engine.&lt;/li&gt;
&lt;li&gt;cron jobs: &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt; coupled with &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;lambda&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;background jobs: &lt;a href="https://aws.amazon.com/sns/" rel="noopener noreferrer"&gt;SNS&lt;/a&gt; coupled with &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;lambda&lt;/a&gt;. One could also use &lt;a href="https://aws.amazon.com/sqs/" rel="noopener noreferrer"&gt;SQS&lt;/a&gt;, which is a real queue system, or a more complex pattern. But I yet have to become more AWS experienced before diving into SQS and its configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anyone experienced with AWS, this may be the usual stack. It took me days to find and understand those solutions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploying the infrastructure
&lt;/h1&gt;

&lt;p&gt;Great! Now, how do I easily deploy and maintain all of that? As a new user, I found the AWS console pretty good. You can create your infrastructure in just a few clicks and even code your lambda functions inside your browser: amazing.&lt;/p&gt;

&lt;p&gt;But, I said I needed the solution to "Be easy to administrate, modify and deploy. Ideally with code.".&lt;/p&gt;

&lt;p&gt;After more research, I understood that what I was looking for was &lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code" rel="noopener noreferrer"&gt;Infrastructure as Code&lt;/a&gt; tools (IaC) to control AWS services. IaC tools can range from python files to configure servers, to gigantic YAML files to deploy AWS services.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right tool for the job: AWS Cloud Development Kit
&lt;/h2&gt;

&lt;p&gt;I found the AWS Cloud Development Kit (CDK) randomly browsing IaC tools. It is mentioned on the &lt;a href="https://www.pulumi.com/docs/intro/vs/" rel="noopener noreferrer"&gt;Pulumi vs. Other Solutions&lt;/a&gt; page which is another IaC tool.&lt;/p&gt;

&lt;p&gt;Here's an example CDK stack creating a lambda that will get called every two minutes:&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;cdk&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;@aws-cdk/core&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;events&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;@aws-cdk/aws-events&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;targets&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;@aws-cdk/aws-events-targets&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;NodejsFunction&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;aws-lambda-nodejs-webpack&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// we define the lambda, it's a local file that exports a `handler` function&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkTwitterLambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodejsFunction&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkTwitterLambda&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;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jobs/checkTwitter.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;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// run jobs/checkTwitter.js every 2 minutes&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&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;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Rule&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ScheduleRule&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;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*/2&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;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTarget&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;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkTwitterLambda&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;Reading this code, as a JavaScript developer, I immediately thought it was the right tool for me. After being able to use JavaScript on the backend (&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;), on the frontend (&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;), now I can even define my infrastructure needs in JavaScript. CDK is also available for TypeScript, Python, Java and C#/.Net (And they do this through code generation with &lt;a href="https://github.com/aws/jsii" rel="noopener noreferrer"&gt;https://github.com/aws/jsii&lt;/a&gt;. Mind, blown.). &lt;/p&gt;

&lt;p&gt;Note: It's amazing how good CDK is as a tool, but how bad they are, for now, SEO wise. It's extremly hard to stumble on CDK if you're searching for infrastructure tools.&lt;/p&gt;

&lt;p&gt;If you're interested in: how CDK works, the only issue I had with it or the other IaC tools I tried. You can read the folded sections.&lt;/p&gt;

&lt;p&gt;Otherwise, feel free to jump into the next part about the full application example!&lt;/p&gt;

&lt;p&gt;
  The only issue I had with CDK
  &lt;br&gt;
I hit a single issue with CDK: They run &lt;a href="https://parceljs.org/" rel="noopener noreferrer"&gt;Parcel&lt;/a&gt; inside &lt;a href="http://docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, by previously mounting your whole JavaScript project as a Docker volume. Unfortunately, on MacOS, mounting big volumes with DOcker is &lt;a href="https://github.com/docker/for-mac/issues/1592" rel="noopener noreferrer"&gt;notoriously slow&lt;/a&gt;. And even with the latest updates to Docker volumes (&lt;a href="https://docs.docker.com/docker-for-mac/mutagen/" rel="noopener noreferrer"&gt;Mutagen-based file synchronization&lt;/a&gt;), it will still be slow. Mounting a small Next.js project would take between 20s and 2 minutes.

&lt;p&gt;&lt;a href="https://github.com/aws/aws-cdk/issues/9120" rel="noopener noreferrer"&gt;I spent&lt;/a&gt; a LOT of time on this issue, and I was lucky to be able to discuss at length with CDK maintainers like &lt;a href="https://github.com/jogold" rel="noopener noreferrer"&gt;Jonathan Goldwasser&lt;/a&gt;. At some point they will provide a Node.js construct that will not use Docker (this is almost released, see: &lt;a href="https://github.com/aws/aws-cdk/pull/9632" rel="noopener noreferrer"&gt;https://github.com/aws/aws-cdk/pull/9632&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In the meantime, I created my own construct with webpack which works pretty well: &lt;a href="https://github.com/vvo/aws-lambda-nodejs-webpack" rel="noopener noreferrer"&gt;https://github.com/vvo/aws-lambda-nodejs-webpack&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;
  Other IaC tools I tried: Architect and Pulumi
  &lt;h2&gt;
  
  
  Architect
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://arc.codes/" rel="noopener noreferrer"&gt;https://arc.codes/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Architect looked like what I needed: control AWS services via YAML files while also getting a local environment replicating your AWS infrastructure.&lt;/p&gt;

&lt;p&gt;I played with it a little, asked some questions on their slack, only to discover that once you were using Architect, your whole codebase should be controlled and deployed by it. You can't easily mix a Next.js codebase deployed on Vercel and cron jobs hosted on AWS crontrolled by Architect.&lt;/p&gt;

&lt;p&gt;Next!&lt;/p&gt;
&lt;h2&gt;
  
  
  Pulumi
&lt;/h2&gt;

&lt;p&gt;The first real Infrastructure as Code (Code as in JavaScript code, not YAML files) tool I tried was &lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt;. Pulumi is multi-cloud. I was happy to find out you could easily create lambda functions on AWS that would get regularly called, just like cron jobs. While also using SQS, the queue from AWS and link it to lambdas as well.&lt;/p&gt;

&lt;p&gt;I had dumb issues with Pulumi: not being able to easily use import statements if you're not using TypeScript, all dependencies from my package.json would be installed on AWS, instead of only the dependencies my jobs are using.&lt;/p&gt;

&lt;p&gt;Pulumi was the first IaC tool I really tried and most probably suffered from my lack of investment in it. I am sure it's a wonderful tool.&lt;/p&gt;

&lt;p&gt;But I needed to try another one. Next!&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  A full application example
&lt;/h1&gt;

&lt;p&gt;Here's the first takeaway. I spent some time creating an example CDK stack you can reuse: &lt;a href="https://github.com/vvo/nextjs-vercel-aws-cdk-example" rel="noopener noreferrer"&gt;https://github.com/vvo/nextjs-vercel-aws-cdk-example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you need a database, crons, or background jobs for your Jamstack application and you do not know where to start: start here and use this example. It will save you time and money. I guarantee you that in just an afternoon work you'll get it working.&lt;/p&gt;

&lt;p&gt;The example provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS stack ready to be deployed with CDK, including a PostgreSQL database, sample crons and background jobs as lambdas&lt;/li&gt;
&lt;li&gt;A Next.js application you can deploy on Vercel, with a button to trigger the sample background job on AWS&lt;/li&gt;
&lt;li&gt;Local equivalents to the AWS services for you developer environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can pick whatever interest you in this example, so that if you're using a different framework (&lt;a href="http://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt;), a different Jamstack platform (&lt;a href="https://netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;), you can still totally benefit from the CDK example stack.&lt;/p&gt;

&lt;p&gt;The example also contains more information on how to run it and links to the right documentation you may need.&lt;/p&gt;

&lt;h1&gt;
  
  
  AWS Cost
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftv98zws5qi6o0a9dyrdk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftv98zws5qi6o0a9dyrdk.png" alt="Screenshot of AWS calculator" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;
The AWS calculator



&lt;p&gt;I am still new to AWS and there are free tiers, and credits, but I still tried to estimate the cost of the application example using the &lt;a href="https://calculator.aws/#/estimate" rel="noopener noreferrer"&gt;AWS pricing calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hypothesis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we use a &lt;a href="https://aws.amazon.com/rds/instance-types/" rel="noopener noreferrer"&gt;t3.micro&lt;/a&gt;, 5GB SSD on-demand instance for the database&lt;/li&gt;
&lt;li&gt;we have 10 cron jobs that runs every 5 minutes, for 5 seconds and uses 300MB of memory each (that's ~90,000 lambda requests)&lt;/li&gt;
&lt;li&gt;we have one background job that runs 10,000 times a month, for 5 seconds, uses 300MB of memory (that's ~10,000 lambda requests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AWS cost would be $16.85/month without the free tier. You can find the computation here: &lt;a href="https://calculator.aws/#/estimate?id=8c37f67bc09a5142d821c25ae3478959ea8ce6f3" rel="noopener noreferrer"&gt;https://calculator.aws/#/estimate?id=8c37f67bc09a5142d821c25ae3478959ea8ce6f3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And, if we switch the t3.micro for a t2.micro and use the free tier, then the monthly AWS cost is $0&lt;/strong&gt;. I've used the t2.micro and frankly it is performant-enough for a product launch.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next steps
&lt;/h1&gt;

&lt;p&gt;This setup is good enough but far from perfect for sure. I expect AWS experts to roast me when they see. I see multiple next steps and will probably write more blog post as I make progress in my AWS usage. Here's a list of things that could be better or just things I don't know yet how to solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets handling&lt;/strong&gt;, as soon as you use Vercel and AWS then you will have "secrets" like database credentials that will be both in &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS secrets manager&lt;/a&gt; and &lt;a href="https://vercel.com/blog/environment-variables-ui" rel="noopener noreferrer"&gt;Vercel environment variables&lt;/a&gt;. Ideally they should be in a single place (AWS secrets manager) and then read from Vercel. I am not sure yet what's the right path to follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual deployment, manual deployments&lt;/strong&gt;, right now whenever I push my code to GitHub, Vercel deploys my application to their servers, fine! But as for AWS I need to manually run &lt;code&gt;yarn cdk deploy&lt;/code&gt; to have my lambdas and infrastructure up to date. Ideally everything (infrastructure, lambdas, migrations, frontend) should be deployed automatically when I push to GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull requests handling&lt;/strong&gt;, if I open a pull request on GitHub, the whole infrastructure, database, frontend, should be deployed in an isolated way. This is not done yet too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling, retries&lt;/strong&gt;, right now if a cron fails or a background job fails, it will be retried, up to a certain point though (&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/invocation-retries.html" rel="noopener noreferrer"&gt;lambda retries&lt;/a&gt;). I know this could bite me in the future, so we'll see.&lt;/p&gt;

&lt;h1&gt;
  
  
  PS: AWS credits 💰💰💰
&lt;/h1&gt;

&lt;p&gt;I promised you you could get up to $5,000 in AWS credits, here's how:&lt;/p&gt;

&lt;p&gt;👉 Secret has over 150+ offers for credits on Algolia, Twilio, Airtable, Notion... &lt;strong&gt;You can save thousands of dollars. Immediately.&lt;/strong&gt; Use my promo link: &lt;a href="https://www.joinsecret.com/?via=vincentvoyer" rel="noopener noreferrer"&gt;https://www.joinsecret.com/?via=vincentvoyer&lt;/a&gt; to get a 20% discount on the platinum or any other Secret plan.&lt;/p&gt;

&lt;p&gt;You can also get $1,000 AWS credits with AWS founders activate, directly on &lt;a href="https://aws.amazon.com/activate/" rel="noopener noreferrer"&gt;aws.com/activate&lt;/a&gt;. And if you've already done that you can still get $4,000 from Secret!&lt;/p&gt;

&lt;p&gt;You don't need to be inside a startup incubator to benefit from those offers.&lt;/p&gt;




&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, follow me on Twitter &lt;a href="https://twitter.com/vvoyer" rel="noopener noreferrer"&gt;@vvoyer&lt;/a&gt;, share the article, and &lt;a href="https://news.ycombinator.com/item?id=24210096" rel="noopener noreferrer"&gt;discuss on Hacker News&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>serverless</category>
      <category>cdk</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Enable network throttling on iPhone in 5 steps</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Thu, 11 Jun 2020 12:41:22 +0000</pubDate>
      <link>https://dev.to/vvo/enable-network-throttling-on-iphone-applications-in-5-steps-ikf</link>
      <guid>https://dev.to/vvo/enable-network-throttling-on-iphone-applications-in-5-steps-ikf</guid>
      <description>&lt;p&gt;It's useful to be able to test your fancy new single page application or progressive web app in different network conditions. On desktop you can do so by &lt;a href="https://developers.google.com/web/tools/chrome-devtools/network#throttle" rel="noopener noreferrer"&gt;using network throttling&lt;/a&gt; on Chrome dev tools for example.&lt;/p&gt;

&lt;p&gt;But on Safari on your iPhone, things are messier. The top result on Google for "ios safari network throttling" gives a Stack Overflow answer that goes like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F79v9oh2i9fiui2jq3v81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F79v9oh2i9fiui2jq3v81.png" alt="Stack Overflow answer" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;😤 Installing a proxy!&lt;/p&gt;

&lt;p&gt;Well turns out there's another way. 😌&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install XCode on your mac&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the Mac App Store on your mac, search for Xcode and install it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6er1bow2miobrwzrjfy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6er1bow2miobrwzrjfy0.png" alt="Xcode on the Mac App Store" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Connect your iPhone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Connect your iPhone to your mac via a USB cable, say yes and confirm any request from your mac or iPhone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Launch XCode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just launch it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Access developer settings on iPhone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that XCode was launched, you should be able to access a new "developer" settings on your iPhone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs5ltg30kdx6f5kb7jy2r.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs5ltg30kdx6f5kb7jy2r.jpeg" alt="Developer settings on iPhone" width="750" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am not even sure you still need XCode running now, you can try to close it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Use the Network Link Conditioner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Inside developer settings, there's a Network Link Conditioner submenu that you can use to throttle your connection on iPhone.&lt;/p&gt;

&lt;p&gt;Here's a full demo: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffi2x765qvghs1agk6gzw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffi2x765qvghs1agk6gzw.gif" alt="Throttling fast.com" width="320" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>safari</category>
      <category>debugging</category>
      <category>network</category>
    </item>
    <item>
      <title>How to solve "window is not defined" errors in React and Next.js</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Wed, 10 Jun 2020 11:16:03 +0000</pubDate>
      <link>https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97</link>
      <guid>https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97</guid>
      <description>&lt;p&gt;Next.js is a React framework with &lt;a href="https://nextjs.org/docs/basic-features/pages#pre-rendering" rel="noopener noreferrer"&gt;pre-rendering&lt;/a&gt; abilities. This means that for every page, Next.js will try to generate the HTML of the page for better SEO and performance.&lt;/p&gt;

&lt;p&gt;This is why, if you're trying to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/Scroll.js&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;scroll&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll!&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;Then it will fail with "ReferenceError: window is not defined":&lt;/p&gt;

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

&lt;p&gt;Because in the Node.js world, window is not defined, window is only available in browsers.&lt;/p&gt;

&lt;p&gt;There are three ways to solve that:&lt;/p&gt;
&lt;h2&gt;
  
  
  1. First solution: typeof
&lt;/h2&gt;

&lt;p&gt;While you can't use:&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="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// browser code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because this would try to compare a non-existent variable (window) to undefined, resulting in the mighty "ReferenceError: window is not defined". You can still use:&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&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;// browser code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because typeof won't try to evaluate "window", it will only try to get its type, in our case in Node.js: "undefined".&lt;/p&gt;

&lt;p&gt;PS: Thanks to &lt;a href="https://dev.to/teamroggers"&gt;&lt;br&gt;
Rogier Nitschelm&lt;/a&gt; for reminding me about this. I initially tried to do &lt;code&gt;if (typeof window !== undefined)&lt;/code&gt; and this failed hard because of the reasons mentioned earlier.&lt;/p&gt;

&lt;p&gt;The other solutions below are more exotic but still worth it.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Second solution: the useEffect hook
&lt;/h2&gt;

&lt;p&gt;The "React" way to solve this issue would be to use the &lt;a href="https://reactjs.org/docs/hooks-effect.html" rel="noopener noreferrer"&gt;useEffect&lt;/a&gt; React hook. Which only runs at the rendering phase, so it won't run on the server.&lt;/p&gt;

&lt;p&gt;Let's update our scroll.js component:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/Scroll.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Scroll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mount&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="nf"&gt;onScroll&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll!&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onScroll&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;unMount&lt;/span&gt;&lt;span class="p"&gt;()&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="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onScroll&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;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;What we've done here is to turn our initial JavaScript file into a true React component that then needs to be added to your React tree via:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/index.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Scroll&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/Scroll&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&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;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;minHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1000px&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&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;Scroll&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="sr"&gt;/div&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; The way we use useEffect in the example is to register and unregister the listeners on mount/unmount. But you could also just register on mount and ignore any other rendering event, to do so you would do this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/Scroll.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Scroll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onFirstMount&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="nf"&gt;onScroll&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll!&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onScroll&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="c1"&gt;// empty dependencies array means "run this once on first mount"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Third solution: dynamic loading
&lt;/h2&gt;

&lt;p&gt;A different solution is to load your Scroll component using &lt;a href="https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr" rel="noopener noreferrer"&gt;dynamic imports&lt;/a&gt; and the &lt;code&gt;srr: false&lt;/code&gt; option. This way your component won't even be rendered on the server-side at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This solution works particularly well when you're importing external modules depending on &lt;code&gt;window&lt;/code&gt;.&lt;/strong&gt; (Thanks &lt;a href="https://twitter.com/jmsunseri" rel="noopener noreferrer"&gt;Justin&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="c1"&gt;// components/Scroll.js&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onScroll&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll!&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onScroll&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Scroll&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;null&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/index.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dynamic&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/dynamic&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;Scroll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/Scroll&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="na"&gt;ssr&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="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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&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;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;minHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1000px&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&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;Scroll&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="sr"&gt;/div&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you do not need the features of useEffect, you can even remove its usage completely as shown here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally&lt;/strong&gt;, you could also load your &lt;code&gt;Scroll&lt;/code&gt; component only in &lt;a href="https://nextjs.org/docs/advanced-features/custom-app" rel="noopener noreferrer"&gt;_app.js&lt;/a&gt; if what you're trying to achieve is to globally load a component and forget about it (no more mount/unmount on page change).&lt;/p&gt;

&lt;p&gt;I have used this technique to display a top level progress bar with &lt;a href="https://github.com/rstacruz/nprogress" rel="noopener noreferrer"&gt;NProgress&lt;/a&gt; in this article: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/vvo" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F24267%2F5a01443f-1a29-4e00-8673-1081deacee87.png" alt="vvo"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/vvo/show-a-top-progress-bar-on-fetch-and-router-events-in-next-js-4df3" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Use NProgress with Next.js (Router and fetch events)&lt;/h2&gt;
      &lt;h3&gt;Vincent ・ Jun 9 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nprogress&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nextjs&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#fetch&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>nextjs</category>
      <category>errors</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Use NProgress with Next.js (Router and fetch events)</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Tue, 09 Jun 2020 14:19:11 +0000</pubDate>
      <link>https://dev.to/vvo/show-a-top-progress-bar-on-fetch-and-router-events-in-next-js-4df3</link>
      <guid>https://dev.to/vvo/show-a-top-progress-bar-on-fetch-and-router-events-in-next-js-4df3</guid>
      <description>&lt;p&gt;Today I was trying to add NProgress &lt;a href="https://github.com/rstacruz/nprogress" rel="noopener noreferrer"&gt;https://github.com/rstacruz/nprogress&lt;/a&gt; to my Next.js project.&lt;/p&gt;

&lt;p&gt;I wanted the progress bar to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;show when switching routes / pages&lt;/li&gt;
&lt;li&gt;show when any &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch" rel="noopener noreferrer"&gt;fetch&lt;/a&gt; call is made&lt;/li&gt;
&lt;li&gt;display only after a delay, I don't want to show a loader at EVERY interaction, only when the requests are "slow"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a demo of how &lt;code&gt;NProgress&lt;/code&gt; looks like:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz27aof371eokm8awgest.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz27aof371eokm8awgest.gif" alt="Demo of NProgress" width="1000" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since I hit met some challenges while implementing all of that, I felt like it would be good to share how I did it. So here it is:&lt;/p&gt;

&lt;p&gt;First, install the &lt;code&gt;nprogress&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then edit or create your &lt;a href="https://nextjs.org/docs/advanced-features/custom-app" rel="noopener noreferrer"&gt;&lt;code&gt;_app.js&lt;/code&gt;&lt;/a&gt; and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// global styles are required to be added to `_app.js` per Next.js requirements.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nprogress/nprogress.css&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;TopProgressBar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;components/TopProgressBar&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="na"&gt;ssr&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="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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&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="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TopProgressBar&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;Component&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&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;/&lt;/span&gt;&lt;span class="err"&gt;&amp;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 we use &lt;a href="https://nextjs.org/docs/advanced-features/dynamic-import" rel="noopener noreferrer"&gt;dynamic imports&lt;/a&gt; and the ssr option to make sure our &lt;code&gt;TopProgressBar&lt;/code&gt; is loaded only in browser environements.&lt;/p&gt;

&lt;p&gt;If you're wondering how relatively loading &lt;code&gt;components/TopProgressBar&lt;/code&gt; works, just configure you &lt;code&gt;jsconfig.json&lt;/code&gt; as shown in the &lt;a href="https://nextjs.org/docs/advanced-features/module-path-aliases" rel="noopener noreferrer"&gt;Next.js documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally create &lt;code&gt;components/TopProgressBar.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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Router&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="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NProgress&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;nprogress&lt;/span&gt;&lt;span class="dl"&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;timer&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;state&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;activeRequests&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;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&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;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;loading&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="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;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;NProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// only show progress bar if it takes longer than the delay&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stop&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;activeRequests&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;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;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;stop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;NProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeStart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;load&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;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stop&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;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stop&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;originalFetch&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="nx"&gt;fetch&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="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;activeRequests&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;activeRequests&lt;/span&gt;&lt;span class="o"&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;originalFetch&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;activeRequests&lt;/span&gt; &lt;span class="o"&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="nx"&gt;activeRequests&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;stop&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &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;null&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 we register to &lt;a href="https://nextjs.org/docs/api-reference/next/router#routerevents" rel="noopener noreferrer"&gt;Next.js router events&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Monkey_patch" rel="noopener noreferrer"&gt;monkey patch&lt;/a&gt; the global fetch. I was worried monkey patching &lt;code&gt;fetch&lt;/code&gt; would fail to register early on but turns out it works 🤷‍♂️!&lt;/p&gt;

&lt;p&gt;As you can see, &lt;code&gt;TopProgressBar&lt;/code&gt; renders nothing, I guess there might be issues of doing things this way so if you encounter some, just let me know!&lt;/p&gt;

&lt;p&gt;That's it!&lt;/p&gt;

&lt;p&gt;If you're wondering if &lt;code&gt;NProgress&lt;/code&gt; is still maintained because of the low number of commits and "high" number of issues, the good news is that they are working on a new version for 2020: &lt;a href="https://github.com/rstacruz/nprogress/pull/218" rel="noopener noreferrer"&gt;https://github.com/rstacruz/nprogress/pull/218&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even if you're not using Next.js, you should be able to adapt this code to your favorite platform or framework.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>nprogress</category>
      <category>nextjs</category>
      <category>fetch</category>
    </item>
    <item>
      <title>Check for extraneous props in React (and save hours of debugging)</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Mon, 11 May 2020 12:19:28 +0000</pubDate>
      <link>https://dev.to/vvo/check-for-extraneous-props-in-react-and-save-hours-of-debugging-1j1l</link>
      <guid>https://dev.to/vvo/check-for-extraneous-props-in-react-and-save-hours-of-debugging-1j1l</guid>
      <description>&lt;p&gt;&lt;small&gt;Photo by &lt;a href="https://burst.shopify.com/@ndekhors?utm_campaign=photo_credit&amp;amp;utm_content=Free+Stock+Photo+of+Hand+Holding+Puzzle+Piece+%E2%80%94+HD+Images&amp;amp;utm_medium=referral&amp;amp;utm_source=credit" rel="noopener noreferrer"&gt;Nicole De Khors&lt;/a&gt; from &lt;a href="https://burst.shopify.com/entrepreneur?utm_campaign=photo_credit&amp;amp;utm_content=Free+Stock+Photo+of+Hand+Holding+Puzzle+Piece+%E2%80%94+HD+Images&amp;amp;utm_medium=referral&amp;amp;utm_source=credit" rel="noopener noreferrer"&gt;Burst&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2022 update:&lt;/strong&gt; TypeScript provides this feature by default when buidling components in .tsx files. So if you're reading this, and not yet using TypeScript, not may be a good time to give it a try (no more proptypes, no more weird setups).&lt;/p&gt;




&lt;p&gt;I have been doing more and more &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; recently and been bit by a nasty habit of me: passing down unknown props to my React components and wondering why it's not working for 10 minutes (OR MORE 😤). If you've already done some React I am sure you'll understand what I mean.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A debugging story&lt;/li&gt;
&lt;li&gt;eslint-plugin-react&lt;/li&gt;
&lt;li&gt;Unknown prop warning (DOM components only)&lt;/li&gt;
&lt;li&gt;
The solution: prop-types-exact

&lt;ul&gt;
&lt;li&gt;ESLint configuration&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Summary&lt;/li&gt;

&lt;li&gt;Bonus: generating Prop Types with VSCode&lt;/li&gt;

&lt;li&gt;Going further: Static Type Checking&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  A debugging story
&lt;/h1&gt;

&lt;p&gt;Let's start with an example.&lt;/p&gt;

&lt;p&gt;On Monday, I create a new Input component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Input.js&lt;/span&gt;
&lt;span class="k"&gt;import&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="s2"&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;PropTypes&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;prop-types&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&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="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;label&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;label&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;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&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="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;propTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use it this way: &lt;code&gt;&amp;lt;Input type="text" label="name" /&amp;gt;&lt;/code&gt;. All good here's my little input with a label.&lt;/p&gt;

&lt;p&gt;On Tuesday, after some heavy code afternoon, I decide that it should be &lt;code&gt;labelText&lt;/code&gt; instead of &lt;code&gt;label&lt;/code&gt; (naming is hard). Fine, let's change the calling code: &lt;code&gt;&amp;lt;Input type="text" labelText="name" /&amp;gt;&lt;/code&gt; and now let's change the implement... "HEY WANNA GRAB A COFFEE?" says a friend? Sure sure ok...&lt;/p&gt;

&lt;p&gt;⏳ 30 minutes later, I go back to coding. "Ok let's see this page I was working on ... Hmm, why is this not working? Why is my label blank?"&lt;/p&gt;

&lt;p&gt;There's no error in console:&lt;/p&gt;

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

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmx4w7xq0dkinnid67o7m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmx4w7xq0dkinnid67o7m.jpg" alt="Alt Text" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"AH! Yes, that prop, it's not the right name, let's fix it... 😖"&lt;/p&gt;

&lt;p&gt;It's easy to spot the mistake if you're reading this right now, but in real life, this happens to me all the time: &lt;strong&gt;passing down unknown props to a React component will not trigger a warning or error by default&lt;/strong&gt;. And this is annoying to the point that I needed a solution.&lt;/p&gt;

&lt;p&gt;Let's see how we can solve that.&lt;/p&gt;

&lt;h1&gt;
  
  
  eslint-plugin-react
&lt;/h1&gt;

&lt;p&gt;I already use &lt;a href="https://github.com/yannickcr/eslint-plugin-react" rel="noopener noreferrer"&gt;eslint-plugin-react&lt;/a&gt; and highly recommend it. Its recommended setting will activate the &lt;a href="https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md" rel="noopener noreferrer"&gt;prop-types&lt;/a&gt; rule that will warn you if some of your props are missing in your prop types definition, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6xky9xx23qkzi7eyzhac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6xky9xx23qkzi7eyzhac.png" alt="estlint-plugin-react example" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is good and you should use it if you like it. But that's not sufficient, it won't warn you about extraneous props when you use your component. It will only warn on missing props validation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Unknown prop warning (DOM components only)
&lt;/h1&gt;

&lt;p&gt;React has a &lt;a href="https://reactjs.org/warnings/unknown-prop.html" rel="noopener noreferrer"&gt;built-in unknown prop warning&lt;/a&gt; activated by default. But this only works on DOM components. So if you try to use &lt;code&gt;&amp;lt;div someProp="yep"&amp;gt;&lt;/code&gt; it will warn you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fucaswtp9q4st1j9zsj96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fucaswtp9q4st1j9zsj96.png" alt="React Unknown Prop warning example" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(only if your prop has a capital letter in it, I just discovered this 🤣)&lt;/p&gt;

&lt;p&gt;But it won't warn you of extraneous properties on your own reusable components.&lt;/p&gt;

&lt;h1&gt;
  
  
  The solution: prop-types-exact
&lt;/h1&gt;

&lt;p&gt;Let's change our previous &lt;code&gt;Input.js&lt;/code&gt; component to use &lt;a href="https://github.com/airbnb/prop-types-exact" rel="noopener noreferrer"&gt;airbnb/prop-types-exact&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="c1"&gt;// Input.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&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;prop-types&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;React&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;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;exactPropTypes&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;prop-types-exact&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&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="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;label&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;label&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;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&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="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;propTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exactPropTypes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's refresh our browser and page using &lt;code&gt;&amp;lt;Input type="text" labelText="name" /&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fptdomj5wfafz9i37gyj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fptdomj5wfafz9i37gyj2.png" alt="prop-types-exact example" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🙌 NICE! Now I can directly see that I passed down some prop that is not recognized by my component: fast and easy debugging instead of a blank console.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint configuration
&lt;/h2&gt;

&lt;p&gt;If you start wrapping your proptypes with &lt;code&gt;exactPropTypes&lt;/code&gt; then &lt;code&gt;eslint-plugin-react&lt;/code&gt; will no more recognize where are your prop types declared. To fix this, add &lt;code&gt;propWrapperFunctions&lt;/code&gt; (an &lt;code&gt;eslint-plugin-react&lt;/code&gt; &lt;a href="https://github.com/yannickcr/eslint-plugin-react#configuration" rel="noopener noreferrer"&gt;configuration&lt;/a&gt;) to your &lt;a href="https://eslint.org/docs/user-guide/configuring#adding-shared-settings" rel="noopener noreferrer"&gt;ESLint settings&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settings&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;propWrapperFunctions&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exactPropTypes&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Using React, Prop Types, ESLint and &lt;code&gt;eslint-plugin-react&lt;/code&gt; you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;know when a proptype is missing&lt;/li&gt;
&lt;li&gt;know if you're using a prop on a component that is not defined as a proptype&lt;/li&gt;
&lt;li&gt;know when a proptypes is not used in your component&lt;/li&gt;
&lt;li&gt;know if you're passing down a wrong proptype (like a number instead of a string)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Bonus: generating Prop Types with VSCode
&lt;/h1&gt;

&lt;p&gt;There's an extension that you can use to generate proptypes easily with VSCode: &lt;a href="https://marketplace.visualstudio.com/items?itemName=suming.react-proptypes-generate" rel="noopener noreferrer"&gt;React PropTypes Generate&lt;/a&gt; and it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu1wfta8gjw0i74ihzi72.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu1wfta8gjw0i74ihzi72.gif" alt="React PropTypes Generate VSCode extension example usage" width="760" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Going further: Static Type Checking
&lt;/h1&gt;

&lt;p&gt;As the &lt;a href="https://reactjs.org/docs/static-type-checking.html" rel="noopener noreferrer"&gt;React documentation says&lt;/a&gt;, on bigger codebases static type checking can help you detect those cases too. As I am not using static type checking I can't tell you if all the cases we saw in this article are covered. If you're using TypeScript or Flow, let me know in the comments how's the experience on extraneous proptypes usage.&lt;/p&gt;

&lt;p&gt;PS: As you guess a friend would not interrupt you in the middle of a coding session BUT that's just for the story 😘&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

</description>
      <category>react</category>
      <category>proptypes</category>
      <category>eslint</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Google sheets: get currency rate exchange for a specific date</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Tue, 28 Apr 2020 19:42:49 +0000</pubDate>
      <link>https://dev.to/vvo/google-sheets-get-currency-rate-exchange-for-a-specific-date-5ab</link>
      <guid>https://dev.to/vvo/google-sheets-get-currency-rate-exchange-for-a-specific-date-5ab</guid>
      <description>&lt;p&gt;Hi there, here's a quick post on how to get and display a currency rate exchange for a specific date in Google Spreadsheets. This is useful in some situations.&lt;/p&gt;

&lt;p&gt;The formula to use is &lt;a href="https://support.google.com/docs/answer/3093281?hl=en" rel="noopener noreferrer"&gt;GOOGLEFINANCE&lt;/a&gt; and combine it with the &lt;a href="https://support.google.com/docs/answer/3098242?hl=en" rel="noopener noreferrer"&gt;INDEX&lt;/a&gt; formula to get only the relevant data.&lt;/p&gt;

&lt;p&gt;Here's an example: &lt;code&gt;=index(GoogleFinance("CURRENCY:USDEUR", "price", DATE(2020, 04, 10)), 2, 2)&lt;/code&gt; would get the USD to EUR rate as for 2020/04/10.&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%2Fx17lp142qc17ryc1j18c.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%2Fx17lp142qc17ryc1j18c.png" alt="Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>googlespreadsheets</category>
      <category>googlefinance</category>
      <category>currencyexchangerate</category>
    </item>
    <item>
      <title>How to do upserts in Knex.js (PostgreSQL)</title>
      <dc:creator>Vincent</dc:creator>
      <pubDate>Sun, 26 Apr 2020 12:11:11 +0000</pubDate>
      <link>https://dev.to/vvo/upserts-in-knex-js-1h4o</link>
      <guid>https://dev.to/vvo/upserts-in-knex-js-1h4o</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr; Since November 2020, you can use Knex.js's onConflict/merge/ignore API features to do upserts in Knex.js: &lt;a href="https://knexjs.org/#Builder-onConflict" rel="noopener noreferrer"&gt;https://knexjs.org/#Builder-onConflict&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Hey there, here's a quick post on something that took me way too long to figure out how to do.&lt;/p&gt;

&lt;p&gt;For my Node.js database needs, I am using &lt;a href="https://knexjs.org/" rel="noopener noreferrer"&gt;Knex.js&lt;/a&gt; together with &lt;a href="https://vincit.github.io/objection.js/" rel="noopener noreferrer"&gt;Objection.js&lt;/a&gt;. My database of choice is &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;. At some point, I needed to do &lt;a href="https://wiki.postgresql.org/wiki/UPSERT" rel="noopener noreferrer"&gt;UPSERTs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's an &lt;code&gt;upsert&lt;/code&gt;? It's a way to express "I have this list of records, identified by this key or combination of keys, please insert them (if they are new) or update them (if they are already existing). &lt;strong&gt;Update&lt;/strong&gt; or &lt;strong&gt;insert&lt;/strong&gt;, that's it.&lt;/p&gt;

&lt;p&gt;Upsert is not part of the SQL standard but luckily this has been implemented in PostgreSQL (and other engines) behind the keywords &lt;code&gt;ON CONFLICT DO UPDATE/NOTHING&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;del&gt;This has yet to be implemented in Knex.js and there's a &lt;a href="https://github.com/knex/knex/pull/3763" rel="noopener noreferrer"&gt;promising pull request&lt;/a&gt; that would implement it for all engines.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;November 2020 update:&lt;/strong&gt; The &lt;a href="https://github.com/knex/knex/pull/3763" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; got merged on Knex and you can now natively do upserts and find or create actions using Knex.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;/p&gt;

&lt;p&gt;Find a user, update the name if they exist, otherwise create the user 👇&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;user&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="nf"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&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="nf"&gt;insert&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;Vincent Voyer&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;vincent@codeagain.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="nf"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&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="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returning&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a user if they do not exist, otherwise do nothing 👇&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="nf"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&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="nf"&gt;insert&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;Vincent Voyer&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;vincent@codeagain.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="nf"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&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="nf"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ You cannot use &lt;code&gt;returning("*")&lt;/code&gt; with &lt;code&gt;ignore()&lt;/code&gt; though. But you can still request your user afterward.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;onConflict/ignore/merge&lt;/code&gt; features are available in Knex.js &amp;gt;= 0.21.10 and well documented here: &lt;a href="https://knexjs.org/#Builder-onConflict" rel="noopener noreferrer"&gt;https://knexjs.org/#Builder-onConflict&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am leaving the previous article content, which discusses how to do upserts before it was available in knex.js here:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;For now, here's what you can do&lt;/strong&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="nx"&gt;Knex&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;knex&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;knex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Knex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;connection&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;DATABASE_URL&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;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_id&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="na"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&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;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;upsert&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;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;knex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`? ON CONFLICT (user_id, team_id)
              DO UPDATE SET
              role = EXCLUDED.role,
              updated_at = CURRENT_TIMESTAMP
            RETURNING *;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;records&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 would insert or update the records, identified by the combination of &lt;code&gt;user_id, team_id&lt;/code&gt;. This means you would need the table &lt;code&gt;roles&lt;/code&gt; to have a unique constraint of &lt;code&gt;user_id, team_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You could also do this:&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="nx"&gt;Knex&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;knex&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;knex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Knex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;connection&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;DATABASE_URL&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;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_id&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="na"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&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;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;team_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;upsert&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;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;knex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`? ON CONFLICT user_id
            DO NOTHING
            RETURNING *;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;knex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;records&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;If you wanted to insert or do nothing (only insert the new records).&lt;/p&gt;

&lt;p&gt;This is the most straightforward solution I have seen which does not try to be smart and generic. You could always wrap all of that and extend the knex query builder if you wanted something more generic and reusable. If you happen to do so or if you have more questions, drop me a comment here!&lt;/p&gt;

</description>
      <category>knex</category>
      <category>upserts</category>
      <category>insert</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
