<?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>Forem: Alex Barashkov</title>
    <description>The latest articles on Forem by Alex Barashkov (@alex_barashkov).</description>
    <link>https://forem.com/alex_barashkov</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%2F86895%2F694d86fe-62a2-40ee-8f86-0013f9018642.jpg</url>
      <title>Forem: Alex Barashkov</title>
      <link>https://forem.com/alex_barashkov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alex_barashkov"/>
    <language>en</language>
    <item>
      <title>Things you might not know about Next Image</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Thu, 22 Jun 2023 13:31:19 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/things-you-might-not-know-about-next-image-5go8</link>
      <guid>https://forem.com/alex_barashkov/things-you-might-not-know-about-next-image-5go8</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51dn6f7j3ey27ndemjxv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51dn6f7j3ey27ndemjxv.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've worked with Next.js, it's likely that you've come across Next Image component. This hassle-free image optimization solution not only provides support for modern formats such as webp and avif but also generates multiple versions tailored to different screen sizes.&lt;/p&gt;

&lt;p&gt;To leverage this magic, simply add the following code to your page:&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;Image&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;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;Page&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;Image&lt;/span&gt;
      &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/profile.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Picture of the author&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, as is the case with any magic, there's a solid foundation of hard work that enables it to function seamlessly. In this article, we're going to explore how Next Image works and clear up some common misconceptions surrounding it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I write content about web architecture and frameworks. Follow me on &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; to stay up to date.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Core Architecture
&lt;/h2&gt;

&lt;p&gt;The underlying architecture of &lt;code&gt;next/image&lt;/code&gt; is primarily made up of three components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Next Image Component&lt;/li&gt;
&lt;li&gt;Image API&lt;/li&gt;
&lt;li&gt;Image Optimizer&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  React Component
&lt;/h3&gt;

&lt;p&gt;The primary function of the component is to generate the correct HTML image output based on the provided properties and to construct multiple URLs to be populated in the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;src&lt;/code&gt; attributes. Here is an example output from the Next Image component:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; 
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Example"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; 
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt; 
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt; 
  &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"async"&lt;/span&gt; 
  &lt;span class="na"&gt;data-nimg=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; 
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color:transparent"&lt;/span&gt; 
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=640&amp;amp;amp;q=75 1x, 
      /_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=1080&amp;amp;amp;q=75 2x"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=1080&amp;amp;amp;q=75"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a closer look at the generated URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;_next&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="nt"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="nc"&gt;.jpg&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;w&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;640&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;75&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This encoded URL accepts two parameters: &lt;code&gt;w&lt;/code&gt; (width) and &lt;code&gt;q&lt;/code&gt; (quality), which are more visible in the decoded version. You can spot that there is no &lt;code&gt;h&lt;/code&gt; (height) attribute, but about that we will talk later in the article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image API
&lt;/h3&gt;

&lt;p&gt;The Next Image API serves as an image proxy, similar to &lt;a href="https://github.com/unjs/ipx" rel="noopener noreferrer"&gt;IPX&lt;/a&gt;. It performs the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepts an &lt;strong&gt;image URL&lt;/strong&gt;, &lt;strong&gt;width&lt;/strong&gt;, and &lt;strong&gt;quality&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Validates parameters&lt;/li&gt;
&lt;li&gt;Determines cache control policies&lt;/li&gt;
&lt;li&gt;Processes the image&lt;/li&gt;
&lt;li&gt;Serves the image in a format supported by the user's browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As things begin to make more sense, let's briefly discuss the final piece of the puzzle before we draw some conclusions from this arrangement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Optimizer
&lt;/h3&gt;

&lt;p&gt;Next Image utilizes different image optimization libraries - Sharp or Squoosh - depending on certain conditions:&lt;/p&gt;

&lt;p&gt;Sharp is a fast and efficient image optimization Node.js module that makes use of the native &lt;a href="https://github.com/libvips/libvips" rel="noopener noreferrer"&gt;libvips&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;Squoosh is a fully node-based image optimization solution. It's slower, but it doesn't require any additional libraries to be installed on a machine. For this reason, Sharp is recommended for production use, whereas Squoosh is used by default in local environments.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I advise using Sharp in local environments as well. While both Sharp and Squoosh optimize images quite similarly, Sharp's compression algorithms can lead to color degradation compared to Squoosh. This can result in visually different behavior between production and local environments, particularly when trying to match the background color of an image with the page background.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Outcomes
&lt;/h2&gt;

&lt;p&gt;Having understood the primary architecture behind &lt;code&gt;next/image&lt;/code&gt;, we can debunk common misconceptions and glean more insights on how to utilize it more effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  next/image does not crop
&lt;/h3&gt;

&lt;p&gt;A common misconception among developers is that &lt;code&gt;next/image&lt;/code&gt; can crop their images. This confusion arises because you can pass width, height, and fill properties to the component, creating an impression that the image has been cropped. In reality, this isn't the case. The Next Image component primarily requires width and height for assigning to the img tag to prevent layout shifts.&lt;/p&gt;

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

&lt;p&gt;As we've already discussed, the Image API does not accept a height parameter, meaning it currently isn't possible to change the original image's aspect ratio. If you don't use the fill property, the image will merely stretch or shrink in the event of width-height mismatches.&lt;/p&gt;

&lt;p&gt;However, if you're using TailwindCSS, it behaves differently due to its default global CSS rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&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 makes layout shift issues harder to detect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Displayed image width ≠ loaded image width
&lt;/h3&gt;

&lt;p&gt;Another potential point of confusion is that the width property passed to &lt;code&gt;next/image&lt;/code&gt; doesn't represent the actual width to which the image will be resized. As we noted from the example at the start of the article, passing &lt;code&gt;width={500}&lt;/code&gt; to a component will result in the image being resized to a width of 640px, as evident in the generated URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/_next/image?url&lt;span class="o"&gt;=&lt;/span&gt;/images/example.jpg&amp;amp;w&lt;span class="o"&gt;=&lt;/span&gt;640&amp;amp;q&lt;span class="o"&gt;=&lt;/span&gt;75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you expect the x2 retina version to utilize an image width of 1000px or 1280px, you're in for a surprise. The actual width used will be 1080px. Naturally, you might wonder where these numbers are coming from.&lt;/p&gt;

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

&lt;p&gt;Next.js resizes images to the closest size from an array of&lt;code&gt;deviceSizes&lt;/code&gt; and &lt;code&gt;imageSizes&lt;/code&gt; that you can define in &lt;code&gt;next.config.js&lt;/code&gt;. By default, these are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;deviceSizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;828&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3840&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;imageSizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;384&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;What's crucial to note here is that using the default configuration can negatively impact performance, leading to a reduced score in Lighthouse's Page Speed Insights. This becomes particularly evident when you attempt to display large images on a page. For instance, if you want to render an image with a width of 1250px, the actual loaded image width will be 1920px. The discrepancy between the required size and the actual loaded size becomes even greater for x2 retina versions, as these will be resized to 3840px. However, you can remedy this by adding more sizes to the &lt;code&gt;deviceSizes&lt;/code&gt; or &lt;code&gt;imageSizes&lt;/code&gt; arrays(&lt;a href="https://nextjs.org/docs/app/api-reference/components/image#devicesizes" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Image optimization can be used without the next/image component
&lt;/h3&gt;

&lt;p&gt;With an understanding of the core architecture, it's easy to see that you can use the Image API without necessarily using &lt;code&gt;next/image&lt;/code&gt;. There are several scenarios in which this can be beneficial.&lt;/p&gt;

&lt;p&gt;First, you can render optimized images inside a canvas. Regardless of whether you're loading images onto a canvas from external sources or from local storage, you can pass the correct URL to the API and have it work seamlessly.&lt;/p&gt;

&lt;p&gt;Additionally, you can use it to optimize OG images or create your own &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; tag-based component for better &lt;a href="https://web.dev/codelab-art-direction/" rel="noopener noreferrer"&gt;art direction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Image API is located under the &lt;code&gt;/_next/image&lt;/code&gt; route and accepts just three additional parameters: URL, width (w), and quality (q).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="sr"&gt;/_next/im&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.com/test.jpg&amp;amp;w=640&amp;amp;q=75&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, the width parameter is checked by the API and can only be a number sourced from either the deviceSizes or imageSizes configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use import for local images
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;next/image&lt;/code&gt;, there are two methods you can use to load local images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;profileImg&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./profile.jpg&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;Page&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;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Using absolute path */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/profile.png"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Picture of the author"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Using imported image via relative path */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;profileImg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Picture of the author"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using an absolute path is common when dealing with local images in examples, tutorials, or even open-source projects. It's easy to assume that there's no significant difference aside from the automatic width/height assignment. However, there is a difference. When you access images by an absolute path from a public folder, Next.js adheres to the cache policies of the destination server, which by default results in a 30-day cache policy rather than &lt;code&gt;public,max-age=31536000,immutable&lt;/code&gt;. Using a 30-day cache policy for image assets can significantly lower your Lighthouse score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Sizes and the 100vw Technique
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;next/image&lt;/code&gt; component accepts a property known as 'sizes', akin to the html img sizes attribute. However, in line with other aspects we've discussed, it performs some unique operations too. The 'sizes' attribute works in concert with 'srcset' and accepts a list of browser conditions and image widths for which they should be activated. If you're unfamiliar with this, I recommend taking a look at &lt;a href="https://www.dofactory.com/html/img/sizes" rel="noopener noreferrer"&gt;these docs&lt;/a&gt;, and this &lt;a href="https://codesandbox.io/s/hungry-brattain-vw78xf?file=/index.html:1653-1685" rel="noopener noreferrer"&gt;codesandbox example&lt;/a&gt;. Here's an example of an image using 'sizes':&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/img/html/vangogh-sm.jpg 120w,
             /img/html/vangogh.jpg 193w,
             /img/html/vangogh-lg.jpg 278w"&lt;/span&gt;
     &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 710px) 120px,
            (max-width: 991px) 193px,
            278px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dive into the details for better understanding. When you utilize Next Image without specifying the 'sizes' property, your 'srcset' will include two URLs: one for the standard version (x1) and another for the Retina version (x2). With this setup, the browser will invariably opt for the Retina version when used on a Retina device. This preference arises due to the use of 1x and 2x syntax within the 'srcset'.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"
    /_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=640&amp;amp;amp;q=75  1x,
    /_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=1080&amp;amp;amp;q=75 2x
  "&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser essentially interprets this as: "Load this URL for 2x pixel density, and this other one for 1x pixel density." Thus, if you have a design where the image version on desktop is smaller than on mobile or tablet, the browser will consistently load the larger version with the default Next Image syntax. Unfortunately, this could result in suboptimal performance and a lower Lighthouse score.&lt;/p&gt;

&lt;p&gt;There is, however, a method to instruct the browser to load images based on suitable width. Instead of providing 1x, 2x parameters to the 'srcset' URL, you specify the width of the image. For example, check these instructions to the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"
    /_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=640&amp;amp;amp;q=75   640w,
    /_next/image?url=%2Fimages%2Fexample.jpg&amp;amp;amp;w=1080&amp;amp;amp;q=75 1080w
  "&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the browser selects the most appropriate image for the current size used on the page. If a mobile image has a width of 600px (1200px for Retina), it will choose the &lt;code&gt;1080w&lt;/code&gt; version. Meanwhile, if a desktop image only uses 300px (600px for Retina), the browser opts for &lt;code&gt;640w&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The advantage of this approach lies in loading the most fitting images for the current screen size, thereby enhancing performance due to reduced image size. Now that we understand the benefits, we can apply this strategy with Next Image using the &lt;code&gt;100vw&lt;/code&gt; trick. While you cannot directly instruct Next Image to use the width (&lt;code&gt;w&lt;/code&gt;) parameters near the URL instead of pixel-density (&lt;code&gt;1x&lt;/code&gt;) options, you can apply a workaround arising from how Next Image is coded:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If your 'sizes' attribute contains &lt;code&gt;vw&lt;/code&gt; numbers, it will only keep those sizes larger than the smallest &lt;code&gt;deviceSize&lt;/code&gt; (640 by default) multiplied by the percentage(&lt;code&gt;100vw&lt;/code&gt; = 1, &lt;code&gt;50vw&lt;/code&gt; = 0.5). Specifying &lt;code&gt;100vw&lt;/code&gt;, you will end up with 8 URLs.&lt;/li&gt;
&lt;li&gt;If your 'sizes' property has non-&lt;code&gt;vw&lt;/code&gt; numbers, your 'srcset' will contain ALL SIZES (i.e., all possible combinations of &lt;code&gt;deviceSizes&lt;/code&gt; and &lt;code&gt;imageSizes&lt;/code&gt;), yielding a total of 16 URLs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To illustrate, let's examine the generated code for &lt;code&gt;100vw&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"100vw"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=640&amp;amp;amp;q=75   640w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=750&amp;amp;amp;q=75   750w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=828&amp;amp;amp;q=75   828w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=1080&amp;amp;amp;q=75 1080w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=1200&amp;amp;amp;q=75 1200w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=1920&amp;amp;amp;q=75 1920w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=2048&amp;amp;amp;q=75 2048w,
    /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=3840&amp;amp;amp;q=75 3840w
  "&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&amp;amp;amp;w=3840&amp;amp;amp;q=75"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you include a &lt;code&gt;px&lt;/code&gt; value within 'sizes'(eg. &lt;code&gt;(max-width: 1024px) 800px, 300px&lt;/code&gt;), the list of URLs expands even further, reaching 16 in a default configuration. Ideally, I would prefer to generate 4 URLs for a specific image, similar to other frameworks, rather than bloating the HTML with many unnecessary options, none of which may be perfectly sized for my needs.&lt;/p&gt;

&lt;p&gt;This discussion underscores a key point: to populate 'srcset' with more versions for better performance across a variety of resolutions, you can simply set 'sizes' to &lt;code&gt;100vw&lt;/code&gt;. This trick forces the creation of URLs for 8 sizes, starting from 640px.&lt;/p&gt;

&lt;p&gt;However, because this method can easily inflate your HTML size - especially if you've added extra &lt;code&gt;imageSizes&lt;/code&gt; or &lt;code&gt;deviceSizes&lt;/code&gt; - it's recommended to apply this approach carefully.&lt;/p&gt;

&lt;p&gt;While I can only speculate about the exact reasoning behind this architecture, I assume that for large-scale projects with various image ratios used in many different places, this approach of generating average-sized versions might prove beneficial. These versions could cater to most scenarios and potentially hit the cache more frequently, all the while maintaining ease of use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Though Next Image simplifies image management and provides significant advantages, it could benefit from additional features like advanced cropping and precise resizing, similar to third-party solutions. Incorporating a specialized  component for fine-tuned art direction would also be advantageous. I'd particularly appreciate an automated method for generating four image versions at 0.25x, 0.5x, 1x, and 2x the supplied width.&lt;/p&gt;

&lt;p&gt;Nevertheless, for most use cases, the developer experience and efficiency of Next Image will more than enough.&lt;/p&gt;

&lt;p&gt;If you enjoyed the article and want to see more web tips, &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Use Google Analytics to uncover the truth about dark and light mode user's preferences</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Thu, 15 Dec 2022 12:00:50 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/use-google-analytics-to-uncover-the-truth-about-dark-and-light-mode-users-preferences-46po</link>
      <guid>https://forem.com/alex_barashkov/use-google-analytics-to-uncover-the-truth-about-dark-and-light-mode-users-preferences-46po</guid>
      <description>&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%2Fyyu2vlcn6eyke31l3x63.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%2Fuploads%2Farticles%2Fyyu2vlcn6eyke31l3x63.jpg" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dark mode has become increasingly popular in recent years, as it can help reduce eyestrain and save battery life on devices. If you have a website or an app, you may want to track whether your users are using dark mode so that you can better understand their preferences and tailor your content accordingly.&lt;/p&gt;

&lt;p&gt;To track the user's dark mode setting, we will use Google Analytics 4 (GA4) and Google Tag Manager (GTM). This process will include the configuration of GTM, the creation of a custom dimension in GA, and, finally, setting up a report for gained data.&lt;/p&gt;

&lt;p&gt;Here are the steps to create a custom variable in GTM to track the user's dark and light theme setting:&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%2Ftwk5lv23dy239vip7nab.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%2Fuploads%2Farticles%2Ftwk5lv23dy239vip7nab.gif" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In your GTM account, go to the "Variables" section and click on the "New" button.&lt;/li&gt;
&lt;li&gt;On the "Create Variable" page, select "Custom JavaScript" as the variable type and name it “User Color Mode”.&lt;/li&gt;
&lt;li&gt;In the "Custom JavaScript" field, enter a function that returns either "dark" or "light" depending on the user's dark mode setting.&lt;/li&gt;
&lt;li&gt;Click on the "Save" button to save the custom variable.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: light)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No Preference&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;Note: &lt;code&gt;window.matchMedia('(prefers-color-scheme: dark)')&lt;/code&gt; shows user preference through an operating system setting (e.g., light or dark mode) or a user agent setting. If you want to track it based on the user’s selected option in your app, you can extend this approach by adding the GA user data programmatically.(GA docs)&lt;/p&gt;

&lt;p&gt;Once you have created the custom variable in GTM to track the user's dark mode setting, you can create a custom tag in GTM that sends the data to GA4 as an event. To create a custom tag in GTM, follow these steps:&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%2Fnrv2ewxl7tb628qv0qry.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%2Fuploads%2Farticles%2Fnrv2ewxl7tb628qv0qry.gif" width="600" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In your GTM account, go to the "Tags" section and click on the "New" button.&lt;/li&gt;
&lt;li&gt;In the "Create Tag" page, select "Google Analytics: GA4 Event" as the tag type.&lt;/li&gt;
&lt;li&gt;In the "Google Analytics Settings" section, select the correct GA4 property from the "Configuration Tag" dropdown. Make sure that you’ve selected the property that corresponds to your website or app.&lt;/li&gt;
&lt;li&gt;In the "Event Parameters" section, enter the following values:

&lt;ul&gt;
&lt;li&gt;Event name: "page_view"&lt;/li&gt;
&lt;li&gt;Event parameters: &lt;code&gt;{ "color_mode": {{User Color Mode}} }&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add trigger "Initialization - All pages"&lt;/li&gt;
&lt;li&gt;Press "Save"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now to see the results in Google Analytics(GA4) we need to add custom dimension. You can create a custom dimension named "User Color Mode" with a user property value of "color_mode." Here are the steps to do 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%2Fuploads%2Farticles%2Fvcrcccuilywpib5x0ri6.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%2Fuploads%2Farticles%2Fvcrcccuilywpib5x0ri6.gif" width="500" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in to your Google Analytics account and navigate to the Configure section.&lt;/li&gt;
&lt;li&gt;Click on Custom Definitions, then click on Custom Dimensions.&lt;/li&gt;
&lt;li&gt;Click the "New Custom Dimension" button.&lt;/li&gt;
&lt;li&gt;In the "Name" field, enter "User Color Mode" as the name of the custom dimension.&lt;/li&gt;
&lt;li&gt;In the "Scope" field, select "User" as the scope for this custom dimension. This will allow the dimension to be associated with individual users of your website.&lt;/li&gt;
&lt;li&gt;In the "Event Parameter" field, enter "color_mode" as the event parameter that will set the value of this custom dimension.&lt;/li&gt;
&lt;li&gt;In the "Active" field, make sure you’ve checked the box to enable this custom dimension.&lt;/li&gt;
&lt;li&gt;Click the "Create" button to save the new custom dimension.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you've set up, you can immediately view the statistics on the "Reports" → "Realtime" page. However, on other pages such as "Explore," you'll need to wait 24 hours before you can start using this new user property.&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%2Fsw85wulx2rjixpbvto9d.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%2Fuploads%2Farticles%2Fsw85wulx2rjixpbvto9d.jpg" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Now you know how to set up Google Analytics 4 and GTM in order to track the user’s dark or light theme setting. Analysis of this data can help you define whether it’s important to implement dark mode for the website or app for your audience.&lt;/p&gt;

&lt;p&gt;If you enjoyed the article and want to see more web tips, &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Advanced web font optimization techniques</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Mon, 28 Nov 2022 15:21:29 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/advanced-web-font-optimization-techniques-2n1f</link>
      <guid>https://forem.com/alex_barashkov/advanced-web-font-optimization-techniques-2n1f</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa10gcerb0j4zzfth0jvk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa10gcerb0j4zzfth0jvk.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Website developers have been using custom fonts for ages. Since custom fonts are not present in the OS, they need to load with the site and, therefore, need to load quickly and display consistently across platforms. Common examples include Google Web Fonts — a dedicated library of web-optimized fonts in the Web Open Font Format (WOFF2), which provides excellent compression to minimize size.&lt;/p&gt;

&lt;p&gt;In practice, however, a Google Web Font is typically 20–30KB. If you multiply that by several different styles and weights, you can quickly get to 100–150KB for your site. Another catch with custom fonts is they cause a visible layout shift during page loading.&lt;/p&gt;

&lt;p&gt;This article shares advanced techniques to make your site faster and create a better UX using web fonts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick basics
&lt;/h2&gt;

&lt;p&gt;Let’s quickly address some basics before we jump to advanced techniques. Here is a checklist of basic best practices you can follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://fonts.google.com/" rel="external noopener noreferrer"&gt;Google Web Fonts&lt;/a&gt; whenever possible.&lt;/li&gt;
&lt;li&gt;If you use a custom font try to pass it through &lt;a href="https://www.fontsquirrel.com/" rel="external noopener noreferrer"&gt;Font squirrel&lt;/a&gt; for better compression and vertical baseline fixes.&lt;/li&gt;
&lt;li&gt;Use WOFF2 if you manually embed a font. WOFF2 has a high adoption rate (over 97%) and provides much better compression than other formats.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;link rel="preload" href="/fonts/usual/usual-semi-bold.woff2" as="font" type="font/woff2" crossorigin="anonymous"&amp;gt;&lt;/code&gt; to prioritize the font in the HTTP queue. &lt;a href="https://web.dev/codelab-preload-web-fonts/" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Make sure the font is cached with &lt;code&gt;cache-control: public, max-age=31536000, immutable&lt;/code&gt;. If you use Vercel, Netlify, or Gatsby Cloud, you likely already have good cache policies.&lt;/li&gt;
&lt;li&gt;Download and serve the font from the same CDN as your website. Again, you get this out of the box with Vercel, Netlify, and Gatsby Cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Now let’s talk about advanced optimization
&lt;/h2&gt;

&lt;p&gt;Here are a few things that will save some KB, make fonts look better, and improve the UX:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fallback font size adjustments&lt;/li&gt;
&lt;li&gt;Custom subsetting&lt;/li&gt;
&lt;li&gt;Vertical baseline fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fallback font metric adjustments
&lt;/h2&gt;

&lt;p&gt;Using custom fonts that are not part of the OS has a tradeoff. Browsers do not know the parameters of a font until it has been loaded; until then, the browser uses a fallback font (e.g., Arial, Times New Roman) to calculate the size of elements that use text on the page. But once the font is loaded, the size is recalculated.&lt;/p&gt;

&lt;p&gt;The difference can be huge, and layout shifts will become visible, especially on a slow connection.&lt;/p&gt;

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

&lt;p&gt;This example compares two fonts—Times New Roman and a custom font called Usual. Both fonts have a font size of 16px and a line height of 1.2 but notice the difference in length they have for the same text.&lt;/p&gt;

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

&lt;p&gt;Recently some new CSS properties to match fallback and custom font metrics have seen wide adoption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/ascent-override" rel="external noopener noreferrer"&gt;ascent-override&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/descent-override" rel="external noopener noreferrer"&gt;descent-override&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/line-gap-override" rel="external noopener noreferrer"&gt;line-gap-override&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/size-adjust" rel="external noopener noreferrer"&gt;line-gap-override&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This sounds great, but do you match them? &lt;a href="https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub" rel="external nofollow noopener noreferrer"&gt;Katie Hempenius and Kara Erickson&lt;/a&gt; on the Google Aurora team have created an algorithm for this, but it needs automated solutions to be its best. The Next.js team recently announced its support for this in the updated &lt;code&gt;@next/font&lt;/code&gt; package in v13. Our team took the same approach and wrapped this algorithm in a CLI that generates CSS to adjust fallback font metrics; we call it Fontpie.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pixel-point/fontpie" rel="noopener noreferrer"&gt;github.com/pixel-point/fontpie&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is very simple to use. Here’s an example command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fontpie ./roboto-regular.woff2 &lt;span class="nt"&gt;--name&lt;/span&gt; Roboto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entering this command will return the following CSS, which you can embed in your project no matter which framework or language it uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Roboto'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;font-display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('roboto-regular.woff2')&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'woff2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Roboto Fallback'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'Times New Roman'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;ascent-override&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;84.57%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;descent-override&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22.25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;line-gap-override&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;size-adjust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;109.71%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Roboto'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;'Roboto Fallback'&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 CSS solution works like this: (1) we declare a new font face—Roboto, (2) then we declare another font face with the name “Roboto Fallback” that uses the local Times New Roman font but with applied metric adjustments. This will completely mitigate any layout shift.&lt;/p&gt;

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

&lt;p&gt;As you can see in the image, Times New Roman with metric adjustments now takes the same amount of space as the Usual font.&lt;/p&gt;

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

&lt;p&gt;This technique is compatible with all modern browsers except Safari.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom subsetting
&lt;/h3&gt;

&lt;p&gt;This technique reduces the number of characters embedded in a font. Fonts typically have many more characters than you need for your project, so using a subset that only includes what you need can significantly reduce the font size.&lt;/p&gt;

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

&lt;p&gt;Google Web Fonts have a nice API to create a font subset. As an example, we will take the font &lt;a href="https://fonts.google.com/specimen/Space+Grotesk" rel="noopener noreferrer"&gt;Space Grotesk&lt;/a&gt;. The Regular (400) font size is 15KB in WOFF2 format. An embedded link will look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css2?family=Space+Grotesk&amp;amp;display=swap"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To define a subset, you just need to add an additional &amp;amp;text parameter to the URL; for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css2?family=Space+Grotesk
&amp;amp;display=swap&amp;amp;text=HelloWorld"&lt;/span&gt;
  &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly useful when you have a single “stylish” headline in a different font from the rest of the site. In that case, you can define a subset that only includes the characters for that headline. In the example above, custom subsetting reduced the font size to 1KB.&lt;/p&gt;

&lt;p&gt;Screenshot below illustrates how characters will look like when the letter does not present in the subset.&lt;/p&gt;

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

&lt;p&gt;Listed below are popular subsets you can copy/paste and insert into Google Web Font or Font Squirrel to get the exact set you want to use.&lt;/p&gt;

&lt;p&gt;Popular subsets; copy/paste and edit them for your needs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower case&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;abcdefghijklmnopqrstuvwxyz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Upper case&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ABCDEFGHIJKLMNOPQRSTUVWXYZ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Number&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0123456789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Upper Accents&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßŒŸ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Lower Accents&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿıœƒ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Alt punctuation&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;¡«»¿‚„‹›
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Math Symbols&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ª¬±µº÷Ωπ‰⁄∂∆∏∑√∞∫≈≠≤≥
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Typographics&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;§©®°¶·†‡•™◊ﬁﬂ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Currency&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$¢&lt;/span&gt;£¥ƒ€
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Punctuation&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="nv"&gt;$%&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;'()*+,-./:;&amp;amp;lt;=&amp;amp;gt;?@[&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="s2"&gt;^_&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;|&lt;span class="o"&gt;}&lt;/span&gt;~–—‘’“”…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Diacriticals&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;¨¯´¸ˆˇ˘˙˚˛˜˝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;For example, the Space Grotesk subset defined below includes Upper case, Lower case, Numbers, Punctuation, and Currency, and the resulting font custom subset is only 6KB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://fonts.googleapis.com/css2?family&lt;span class="o"&gt;=&lt;/span&gt;Space+Grotesk&amp;amp;display&lt;span class="o"&gt;=&lt;/span&gt;swap&amp;amp;text&lt;span class="o"&gt;=&lt;/span&gt;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!%22#&lt;span class="nv"&gt;$%&lt;/span&gt;&amp;amp;&lt;span class="s1"&gt;'()*+,-./:;%3C=%3E?@[\]^_%60{|}~%E2%80%93%E2%80%94%E2%80%98%E2%80%99%E2%80%9C%E2%80%9D%E2%80%A6$
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom fonts subsetting
&lt;/h3&gt;

&lt;p&gt;Things get worse if you use custom fonts, like those available in Adobe Fonts. Many of these fonts are not optimized for web use and contain many unnecessary characters. These fonts can easily reach 30KB. Font squirrel allows you to easily create custom font subsets; just drop your font there in TTF or OTF format, enable Expert mode and custom subsetting.&lt;/p&gt;

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

&lt;p&gt;Creating subsets is easy; simply check the subsets you want to keep or write an exact character in the Single Characters field. For example, the copyright symbol is presented in the “Typographics” subset type, but if you only need ©, add it to the Single Characters field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vertical baseline fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fixing fonts with Font Squirrel
&lt;/h3&gt;

&lt;p&gt;Another potential issue when using custom fonts, or those not well-optimized, is that the vertical baseline may not be correct. For example:&lt;/p&gt;

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

&lt;p&gt;Using these fonts can result in spacing inconsistencies like above, where the padding at the top is smaller than that at the bottom, while in CSS, the padding is the same.&lt;br&gt;
Sometimes, passing the font over Font Squirrel may automatically fix this problem.&lt;/p&gt;

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

&lt;p&gt;If that doesn’t work, you can address it with Custom Adjustments. Though it takes time to find the right combination of metrics, it can solve the issue.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://seek-oss.github.io/capsize/" rel="external noopener noreferrer"&gt;Capsize&lt;/a&gt; is a library that uses font metadata, so text can be sized according to the height of its capital letters while trimming the space above capital letters and below the baseline. It applies CSS rules with :before and :after pseudo-elements, adjusting the margin-top and margin-bottom so the text can be perfectly sized inside the box, taking the whole space.&lt;/p&gt;

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

&lt;p&gt;Example result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;For&lt;/span&gt; &lt;span class="err"&gt;48&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;
&lt;span class="nc"&gt;.css-dpa7xb&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.1432em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.css-dpa7xb&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.2142em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;For&lt;/span&gt; &lt;span class="err"&gt;24&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;

&lt;span class="nc"&gt;.css-1m2jnlz&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.322em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.css-1m2jnlz&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.393em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an amazingly easy-to-use solution that works 100% of the time. It just requires additional CSS code for each font size used on a page. I would still recommend trying Font Squirrel auto-fixes first and going for Capsize if necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Let’s recap what we’ve learned in this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adjusting fallback font metrics is a simple, quick fix that you can use in every project with the help of &lt;a href="https://github.com/pixel-point/fontpie" rel="noopener noreferrer"&gt;Fontpie&lt;/a&gt; or &lt;code&gt;next/font&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Remove font subsets irrelevant to your use case, especially if a custom stylish font is used in the Hero section for design purposes only.&lt;/li&gt;
&lt;li&gt;Keep an eye on the font's vertical baseline and try to fix it if you realize you have to keep padding within a button different from the top and bottom to make the text look centered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using those techniques can fix visual issues of your font while saving additional KB. If you enjoyed this article and want more web tips, &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Optimization techniques for Rive animations in React apps</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Thu, 27 Oct 2022 13:01:22 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/optimization-techniques-for-rive-animations-in-react-apps-1a8p</link>
      <guid>https://forem.com/alex_barashkov/optimization-techniques-for-rive-animations-in-react-apps-1a8p</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixhcd366pclhixb8lmby.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixhcd366pclhixb8lmby.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Rive?
&lt;/h2&gt;

&lt;p&gt;Rive is a modern tool for creating well-performing interactive animations that you can run anywhere. It is simple to use, works great with vector graphics, has a nice developer toolkit and many powerful features like state machine and mesh deformation. At &lt;a href="https://pixelpoint.io/" rel="noopener noreferrer"&gt;Pixel Point&lt;/a&gt;, we often use it for building stunning marketing websites, including our own one.&lt;/p&gt;

&lt;p&gt;See a lot of fascinating examples in &lt;a href="https://rive.app/community/" rel="noopener noreferrer"&gt;Rive community library&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can we optimize?
&lt;/h2&gt;

&lt;p&gt;Rive tells us that all animations are already performing great, and &lt;strong&gt;it’s true&lt;/strong&gt;. Just see this test from Rive team comparing Lottie and Rive.&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;So I won’t be telling you how to draw and animate in Rive. I will tell you how can you optimize the loading of Rive animations to achieve a better user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Room for improvement
&lt;/h3&gt;

&lt;p&gt;It’s very simple to use Rive in React project. Following the official guide, you can do it this way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rive&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rive-app/react-canvas&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Simple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rive&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.rive.app/animations/vehicles.riv"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the component is mounted, Rive loads a separate &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;WASM&lt;/a&gt; runtime library during initial initialization. Rive uses this library to control animations and interact with them. I suppose it is also the reason Rive performance is so much better compared to the alternative solutions like Lottie. This WASM module is not small and has a size of &lt;strong&gt;78KB&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;However, in that case WASM module and .riv animation file will start loading only after React is executed, and if you have Rive animation in the Hero section of your page, it may appear only after 4-5 seconds if the page size is rather big.&lt;/p&gt;

&lt;p&gt;Considering that you use modern frameworks that give you SSG/SSR, initial HTML will come to you quite fast, but then you will wait for React hydration to initialize Rive animation.&lt;/p&gt;

&lt;p&gt;After measuring the time needed for the initial page loading, component mounting and animation playing, I’ve got the following numbers on a sample project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;span class="nx"&gt;Rive&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="nx"&gt;playing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are average numbers. They can be different depending on your location and connection. However, it’s obvious that there is room for improvement. My goal was to make the diff between component mounting and animation playing as short as possible, so that’s what can you do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Host WASM yourself
&lt;/h3&gt;

&lt;p&gt;If you look at the network tab closer, you can see that Rive does not bundle the WASM module into your project and always loads it from&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//unpkg.com/@rive-app/canvas@1.0.64/rive.wasm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates an additional connection that I would like to avoid and serve all files from the same CDN. In order to fix it, we can &lt;a href="https://github.com/rive-app/rive-wasm/blob/master/js/CHANGELOG.md#0714" rel="noopener noreferrer"&gt;use the following API&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;riveWasmUrl&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rive-app/canvas/rive.wasm&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;RuntimeLoader&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="s1"&gt;rive-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;RuntimeLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWasmUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;riveWasmUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can add this code to a page component file where Rive is used. It won’t work unless you add an additional webpack URL loader for wasm files into the webpack configuration.&lt;/p&gt;

&lt;p&gt;So, if you use Gatsby, do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onCreateWebpackConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;loaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWebpackConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rules&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;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;wasm$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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="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;And this, if you use Next.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;wasm$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url-loader&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it’s better, but it was not the biggest issue here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preload WASM
&lt;/h3&gt;

&lt;p&gt;The next thing we can do is preload WASM module with &lt;code&gt;link rel="preload"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Since we already patched webpack to load WASM, now it’s very simple to preload it. Add it to the main layout if you use Rive everywhere or set it in the React Helmet on pages where you need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;riveWasmUrl&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rive-app/canvas/rive.wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;riveWasmUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fetch"&lt;/span&gt; &lt;span class="na"&gt;crossOrigin&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Preload .riv animation
&lt;/h3&gt;

&lt;p&gt;The same thing you can do for &lt;code&gt;.riv&lt;/code&gt; file on a page where it is played.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/path-to-rive-file/hero.riv"&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fetch"&lt;/span&gt; &lt;span class="na"&gt;crossOrigin&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;I could achieve almost 0 difference between React component mounting and Rive animation playing with this solution. The remaining difference was about 30ms.&lt;/p&gt;

&lt;p&gt;I also have some other tricks that can help you with certain conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rive lazy loading
&lt;/h2&gt;

&lt;p&gt;If you use small animations across the page, it is easy to load all of them in advance. However, if you have some complex graphics and animations in Rive, it could take easily 20KB+. For that reason, I recommend using Intersection Observer API to load them only when they are visible.&lt;/p&gt;

&lt;p&gt;In order to achieve it, we need to understand how the Rive react component works. Based on Rive source code, it loads &lt;code&gt;.riv&lt;/code&gt; file on component mount, meaning that in order to prevent additional files from loading, we need to prevent rendering of the &lt;code&gt;&amp;lt;RiveComponent&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, using &lt;code&gt;react-intersection-observer&lt;/code&gt;, we can subscribe to the visibility of the section with animation.&lt;/p&gt;

&lt;p&gt;And the last bit is to wrap RiveComponent with &lt;code&gt;&amp;lt;ImagePlaceholder&amp;gt;&lt;/code&gt; to prevent layout shifts on the page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ImagePlaceholder is a simple solution that uses a svg placeholder to maintain the needed aspect ratio of the image. As an alternative, you can use a modern &lt;code&gt;aspect-ratio&lt;/code&gt; CSS property (that still does not work in Safari, unfortunately) or an old school &lt;code&gt;padding-bottom&lt;/code&gt; solution. You can find the source code in the demo or in our &lt;a href="https://github.com/pixel-point/pixelpoint-website/blob/main/src/components/shared/image-placeholder/image-placeholder.jsx" rel="noopener noreferrer"&gt;website&lt;/a&gt; repo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below is an example app that illustrates how it works (&lt;a href="https://codesandbox.io/s/confident-hill-cfwi8z?file=/src/App.js" rel="noopener noreferrer"&gt;See demo&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&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;useRive&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="s1"&gt;@rive-app/react-canvas&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;useInView&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="s1"&gt;react-intersection-observer&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;riveBotAnimationUrl&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bot.riv&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;ImagePlaceholder&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ImagePlaceholder&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;ART_BOARD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HeroBot&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;STATE_MACHINE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;State Machine 1&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;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RiveComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rive&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;riveBotAnimationUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stateMachines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STATE_MACHINE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;artboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ART_BOARD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isInView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useInView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;triggerOnce&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="c1"&gt;// rootMargin: "200px"&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Rive Lazy Loading Example&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Scroll down the page and look at network tab&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"animation-container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ImagePlaceholder&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isInView&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RiveComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ImagePlaceholder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Note: Rive WASM module also loads automatically on a first RiveComponent initialisation, so when postponing the rendering, you also postpone the loading of quite a large runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rive, React.lazy and Suspense
&lt;/h2&gt;

&lt;p&gt;Last bit of optimisation that is very useful in case you don’t want to show Rive animations at all on certain devices is using React 18 Suspense and lazy component loading. The architecture behind that solution is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Move the component that loads, controls and plays Rive animation into a separate React component.&lt;/li&gt;
&lt;li&gt;Import it, using React.lazy. This will cause a creation of a separate JS bundle.&lt;/li&gt;
&lt;li&gt;Wrap lazy loaded component with Suspense.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RiveAnimation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;components/rive-animation&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;Section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="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;isMobile&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"my-example.png"&lt;/span&gt;&lt;span class="p"&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="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RiveAnimation&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&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="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;Section&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find a full example of this solution in a great demo by &lt;strong&gt;&lt;a href="https://twitter.com/PaulieScanlon" rel="noopener noreferrer"&gt;Paul Scanlon&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/d7935306ee974c0cb44aa73ebeaa22c9" rel="noopener noreferrer"&gt;Rive animation component&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/PaulieScanlon/rise-of-the-robots/blob/main/src/sections/gatsby-section.js#L9" rel="noopener noreferrer"&gt;Section component&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com/demos/rise-of-the-robots/" rel="noopener noreferrer"&gt;Demo site&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That’s it!
&lt;/h2&gt;

&lt;p&gt;Let’s sum up everything on the optimal usage of Rive with React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host WASM runtime library yourself. It will prevent an additional http connection with an external unpkg server where this library hosted by default.&lt;/li&gt;
&lt;li&gt;Preload .riv and WASM files in case you have Rive animation in the Hero section of the page. It will drastically decrease loading and execution times.&lt;/li&gt;
&lt;li&gt;Create a separate bundle for Rive using React lazy and Suspense to avoid loading the library when the network is slow or on mobile devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you enjoyed the article and want to see more tips on web performance, &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>react</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Gatsby Graphql schema customization for beginners</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Mon, 16 May 2022 15:57:11 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/gatsby-graphql-schema-customization-for-beginners-ngg</link>
      <guid>https://forem.com/alex_barashkov/gatsby-graphql-schema-customization-for-beginners-ngg</guid>
      <description>&lt;p&gt;Gatsby has one big difference from other frameworks: it has a built-in Graphql data layer. Such a layer opens a lot of possibilities and is a key part of the big ecosystem of plugins. You can fetch data from CMS using Graphql, query images, query markdown based content with a help of a variety of plugins.&lt;/p&gt;

&lt;p&gt;It’s all possible because those plugins fetch data from sources and transform it into Graphql Nodes during build time. In most cases, it’s more than enough, and you even don’t need to be a master of Graphql to use Gatsby and build cool things.&lt;/p&gt;

&lt;p&gt;But today I want to talk about Gatsby’s Schema Customization API, since it could drastically improve the quality of projects in certain cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iK0ErzRF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rk7pzyrm5smgyzxp56fs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iK0ErzRF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rk7pzyrm5smgyzxp56fs.jpg" alt="Image description" width="880" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Gatsby’s Schema Customization API?
&lt;/h2&gt;

&lt;p&gt;This API allows to interact with a Graphql data layer and customize it the way you want. It could help extend the current Graphql types or create completely new ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;p&gt;As a beginner, you would probably benefit from using the following common scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replacing data in the existing Graphql field&lt;/li&gt;
&lt;li&gt;Adding a new field to existing Graphql type and filling it with some data&lt;/li&gt;
&lt;li&gt;Defining relationships between Graphql types&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Replacing data in the existing Graphql field&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This technique is really helpful when you want to define a default value for an existing field. For example, you have a blog that has articles coming from markdown, and some of them have a flag &lt;code&gt;draft: true&lt;/code&gt; but others do not. For better filtering, you want to make sure that every blog post has a draft field with either &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;true&lt;/code&gt; value in Graphql.&lt;/p&gt;

&lt;p&gt;There are two ways to achieve that. First one is using &lt;code&gt;onCreateNode&lt;/code&gt; API (&lt;a href="https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/#onCreateNode"&gt;docs&lt;/a&gt;) that is provided by Gatsby. Just go to &lt;code&gt;gatsby-node.js&lt;/code&gt; and add these lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onCreateNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createNodeField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actions&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createNodeField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isDraft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDraft&lt;/span&gt; &lt;span class="o"&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="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 code will be invoked on each Node creation, so it’s good to filter nodes there by certain parameters to add a field only to the relevant nodes. It’s a quick solution, but it is a bit raw. As you can see, the original &lt;code&gt;isDraft&lt;/code&gt; field from markdown frontmatter data remains untouched, and we create a new one in that node under &lt;code&gt;fields&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y-lc7jck--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mm3kiuiph86nfjbi9trp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y-lc7jck--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mm3kiuiph86nfjbi9trp.png" alt="" width="880" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For better performance, we can use a more granular approach by overriding the original field with a custom resolver. This method requires some more knowledge. We will need to know the exact Graphql type we want to modify. To know the exact name of the type, we can run Gatsby with a special flag &lt;code&gt;GATSBY_GRAPHQL_IDE=playground npm start&lt;/code&gt;. This way we will see GraphQL Playground instead of GraphqiQL IDE on &lt;code&gt;[http://localhost/__graphql](http://localhost/__graphql)&lt;/code&gt;, and we can get the required information there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qz1TfshQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bdpayvoetq7k94eoljs2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qz1TfshQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bdpayvoetq7k94eoljs2.png" alt="" width="880" height="561"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;Schema&lt;/code&gt; panel, find a type using &lt;code&gt;Ctrl+F&lt;/code&gt;. Once we have found it, we can see that the type we’re looking for is &lt;code&gt;MdxFrontmatter&lt;/code&gt; and the field isDraft has a &lt;code&gt;Boolean&lt;/code&gt; type. So with a help of createResolvers API (&lt;a href="https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/#createresolvers-api"&gt;docs&lt;/a&gt;) declared in &lt;code&gt;gatsby-node.js&lt;/code&gt; we can define a default value for the field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createResolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;createResolvers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createResolvers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;MdxFrontmatter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;isDraft&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="s1"&gt;Boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;isDraft&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;isDraft&lt;/span&gt; &lt;span class="o"&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="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;As you can see, there are no more null values in isDraft field and it has either &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;true&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--glv-0Peh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhkqk92ob7r6aib2do7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--glv-0Peh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhkqk92ob7r6aib2do7p.png" alt="Image description" width="880" height="570"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Add a new field to the existing Graphql type and fill it with some data
&lt;/h2&gt;

&lt;p&gt;We just figured out how to modify the existing data, but let’s go further and add a completely new field. Let’s look at this example from our website. We have case studies for open source projects stored in markdown, and we want to have the number of stars from GitHub to be fetched during build time for each one of them. It’s a perfect task for Gatsby Graphql schema customization.&lt;/p&gt;

&lt;p&gt;So for each case study I want to declare a field &lt;code&gt;githubStars&lt;/code&gt; and tell Gatsby to fetch data from the GitHub API. We already used createResolvers function for field modification, but it also could add a new field.&lt;/p&gt;

&lt;p&gt;From Graphql Playground we can grab a type that used for markdown files, which will be &lt;code&gt;Mdx&lt;/code&gt;, and then add a new field this way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createResolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;createResolvers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createResolvers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Mdx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;githubStars&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="s1"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileAbsolutePath&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;githubUsername&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;githubRepoName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOpenSource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frontmatter&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;fileAbsolutePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/case-studies/&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;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;isOpenSource&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;githubUsername&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;githubRepoName&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;`https://api.github.com/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;githubUsername&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;githubRepoName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
              &lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stargazers_count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NumberFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stargazers_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to fetch GitHub stars for case study "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;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;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;Resolve function receives all node data, so it’s easy to filter mdx files based on certain rules e.g., file location folder. Resolver can be an async function so it’s ok to do external calls from there too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SYTt2bfh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4v1cyngghkwabi3taf44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SYTt2bfh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4v1cyngghkwabi3taf44.png" alt="" width="880" height="570"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Define relationships between Graphql types
&lt;/h2&gt;

&lt;p&gt;Another common use case. You have a blog in markdown, and you have a list of authors in JSON. You want to fetch all data from a Graphql layer, so the author is nested to a blog post data. This could be achieved with &lt;code&gt;createSchemaCustomization&lt;/code&gt; hook from &lt;code&gt;gatsby-node.js&lt;/code&gt; using &lt;code&gt;createTypes&lt;/code&gt; API (&lt;a href="https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/#createSchemaCustomization"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The structure of the JSON author object could be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// content/posts/post-authors.json&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;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alex Barashkov&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;photo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../src/images/post-authors/alex-barashkov.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CEO at Pixel Point and software engineer with 10+ years of web development experience. Currently focused on React, Next.js, Gatsby.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twitterUrl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://twitter.com/alex_barashkov&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;more&lt;/span&gt; &lt;span class="nx"&gt;authors&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blog post content, however, could be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content/posts/example.md&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Taking automated web page screenshots with Puppeteer and Sharp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="nx"&gt;tutorial&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;how&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="nx"&gt;perfect&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;resolution&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="nx"&gt;screenshots&lt;/span&gt; &lt;span class="nx"&gt;automatically&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;fuss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;just&lt;/span&gt; &lt;span class="nx"&gt;works&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Alex&lt;/span&gt; &lt;span class="nx"&gt;Barashkov&lt;/span&gt;
&lt;span class="nx"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jpg&lt;/span&gt;
&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Development&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we have a name of the author in a frontmatter post data and the same name in &lt;code&gt;authors.json&lt;/code&gt;. Now the goal is to make the author accessible via Graphql as a nested object when we fetch post data.&lt;/p&gt;

&lt;p&gt;Note: For blog posts we use &lt;code&gt;gatsby-plugin-mdx&lt;/code&gt;, for sourcing JSON data to Graphql - &lt;code&gt;gatsby-transformer-json&lt;/code&gt;. See the full example of the project &lt;a href="https://github.com/pixel-point/pixelpoint-website"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a config from the &lt;code&gt;gatsby-config.js&lt;/code&gt; that makes this sourcing happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gatsby-source-filesystem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/content/posts`&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="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gatsby-source-filesystem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post-authors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/content/posts/post-authors.json`&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;We use the same strategy as before: let’s open Graphql Playground. We already know the Graphql type of markdown files, but we need to find a type of JSON based nodes. In our case, it will be &lt;code&gt;PostAuthorsJson&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Having that information, we can add &lt;code&gt;createSchemaCustomization&lt;/code&gt; to &lt;code&gt;gatsby-node.js&lt;/code&gt; and use a function called &lt;code&gt;createTypes&lt;/code&gt;. That technique allows to modify the Graphql schema.&lt;/p&gt;

&lt;p&gt;Note: If you want to override the existing Graphql type completely, use &lt;code&gt;@dontInfer&lt;/code&gt; directive near the type definition (&lt;a href="https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/#opting-out-of-type-inference"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;There are some options to define nested relationships manually. However, the simplest option is to use a built-in &lt;code&gt;@link&lt;/code&gt; directive. Think about it as a helper that does a mapping between two Graphql types based on provided foreign key relationships.&lt;/p&gt;



&lt;p&gt;Let’s check it out. Add this code to the &lt;code&gt;gatsby-node.js&lt;/code&gt; and see the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSchemaCustomization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTypes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;createTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    type Mdx implements Node { 
      author: PostAuthorsJson @link(by: "name", from: "frontmatter.author")
    }
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now authors are linked to markdown nodes. This way you can fetch nested data and even filter results by author’s information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---KqYCObn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bw7opjy49hcka4ocqvf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---KqYCObn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bw7opjy49hcka4ocqvf7.png" alt="" width="880" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Now you know how to modify the Gatsby Graphql schema, and I hope you find it useful for your project. All three examples are available &lt;a href="https://github.com/pixel-point/pixelpoint-website"&gt;here&lt;/a&gt; in our GitHub website repository.&lt;/p&gt;

&lt;p&gt;Want to learn more about Gatsby, Next.js, React and Headless CMS follow me on &lt;a href="https://twitter.com/alex_barashkov"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Comparing Gatsby and Next.js for website development</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Tue, 22 Feb 2022 15:03:30 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/comparing-gatsby-and-nextjs-for-website-development-13b7</link>
      <guid>https://forem.com/alex_barashkov/comparing-gatsby-and-nextjs-for-website-development-13b7</guid>
      <description>&lt;p&gt;Gatsby and Next.js, from a first glance, could look pretty much the same. Both are React-based frameworks, do SSR, SSG, have big communities. We actively use both technologies in our &lt;a href="https://pixelpoint.io/" rel="noopener noreferrer"&gt;agency&lt;/a&gt;, but we think that they fit better for different use cases. I often get asked why we use Gatsby instead of Next.js for websites development? This article will explain it in detail.&lt;/p&gt;

&lt;p&gt;Choosing the technology for website development, we need to keep in mind a few factors&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speed of development&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Editorial experience&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customization&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how a feature comparison table between two frameworks might look like. They are pretty much the same, aren’t they?&lt;/p&gt;

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

&lt;p&gt;However, those minor differences could cause a huge impact on website development.&lt;/p&gt;

&lt;p&gt;Now, let’s go over what needs to be on a website and check how the frameworks handle the same tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything begins or ends with Favicon
&lt;/h2&gt;

&lt;p&gt;A simple, tiny thing that every website has. But, in order to be compliant with different use cases, OS, Browsers, you need to have usually more than one - 16x16, 32x32, 180x180, 512x512, etc. It’s nice when you don’t need to care about it&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;...and with Gatsby, you don’t&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Change a single line in &lt;code&gt;gatsby-config.js&lt;/code&gt; and upload png/jpg/svg based favicon...That’s it. Gatsby will generate a set of relevant icons following best practices optimizing the image without any additional work.&lt;/p&gt;

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

&lt;p&gt;...&lt;strong&gt;but what about Next.js?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no opinionated way that Next.js recommends for this. &lt;a href="https://www.google.com/search?q=next+js+favicon&amp;amp;rlz=1C5CHFA_enFR972FR972&amp;amp;oq=nextjs+favicon&amp;amp;aqs=chrome.1.69i57j0i10i67j0i10i512l2j0i10i22i30l3.4091j0j7&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8" rel="noopener noreferrer"&gt;Try to Google it&lt;/a&gt; and see how different the answers are, e.g., this &lt;a href="https://stackoverflow.com/questions/56213019/how-to-add-a-favicon-to-a-next-js-static-site" rel="noopener noreferrer"&gt;Stackoverflow Thread&lt;/a&gt;. Everything has to be done manually - image optimization, image resizing, embedding the proper tags using &lt;code&gt;&amp;lt;Head&amp;gt;&lt;/code&gt; tag. As an option, you can use favicons generators services like &lt;a href="https://realfavicongenerator.net/" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Future is here with image optimization
&lt;/h2&gt;

&lt;p&gt;Both do a lot of magic, tuning images with the sharp library, bringing the support for modern image file formats such as webp and avif, which results in smaller files’ sizes and a faster website loading speed. &lt;/p&gt;

&lt;p&gt;Both have their own image components, &lt;code&gt;next-image&lt;/code&gt;, and &lt;code&gt;gatsby-image&lt;/code&gt;, with a similar API. But there are a few differences. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is next-image good?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;next-image&lt;/code&gt;  is just a component when the actual image optimization happens via API route that accepts query string parameters and returns a processed image, e.g., &lt;code&gt;/_next/image?url=http://example.com/avatar.png&amp;amp;w=32&amp;amp;h=32&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I like this architecture for it also brings additional flexibility in terms of using optimized images without using a react component.&lt;/p&gt;

&lt;p&gt;Another thing worth mentioning: &lt;code&gt;next-image&lt;/code&gt; requires you to specify the width/height of the image, which is not the case when you fetch data from a CMS unless you use &lt;code&gt;layout="fill",&lt;/code&gt; but having done that, you are required to manually handle the logic of the image wrapper. So to avoid layout shifts, you fetch the image from a CMS, get their width and height, then, for example, use a CSS aspect-ratio property or go with the SVG hack as &lt;code&gt;gatsby-image&lt;/code&gt; does automatically to maintain the original proportions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;or gatsby-image is better?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gatsby-image&lt;/code&gt; does not have that API endpoint and does the magic behind the scene using the power of their graphql data layer and different transformer plugins. Everything works out of the box without additional configuration. But there is one more thing that Gatsby can do better - &lt;a href="https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/#withartdirection" rel="noopener noreferrer"&gt;image art direction&lt;/a&gt;. Art direction aims to define multiple image srcsets for different screen sizes that could be cropped and positioned differently. The typical use case for this is when you have a big diagram, let’s say on the home page, but on mobile, it will be too small if we just scale it down. As a solution, you might pass a secondary image with increased diagram labels to the srcset optimized for mobile devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importance of CMS Integrations
&lt;/h2&gt;

&lt;p&gt;For the best customer experience, it’s precious to provide the greatest flexibility for editors and solid CMS integration in place. On the websites, we build every word and page can be edited via CMS and any additional metadata - page URL, meta tags, og tags, etc. We use Headless WP most of the time, but sometimes do the job with Contentful, Strapi or Prismic, so having a flexible and straightforward way for data fetching from different platforms is crucial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gatsby and the power of plugins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Gatsby, the integration is a matter of plugin installation and providing API keys. No need to deal with SDKs and nerding through API docs. Everything will be fetched and added to a unified Gatsby Graphql Layer. There are so many plugins that you can find a source plugin literally for anything. The client uses some recruiting platform and wants to display a jobs list on his own website as well? Not a problem. Does he plan to display a list of Github repositories with stars counter - just go and grab the plugin for this. Data will be added to Graphql, and you wouldn’t need to worry about the learning curve for understanding various APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkowoppssabmetwn78jzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkowoppssabmetwn78jzi.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Example of fetching data with Gatsby Graphql using gatsby-source-wordpress plugin&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js tailored approach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next.js does not have a plugins ecosystem, so for fetching data from a CMS, we have to find SDK, learn an API, think about the reusability of that integration within the website on different pages, probably make some SDK wrappers for common use cases, or HOC. Maybe it’s ok for small sites, but for large ones it will require spending some time thinking about the overall data fetching architecture and scalability of the solution.&lt;/p&gt;

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

&lt;p&gt;Okay, let’s step back here because I’m sure that many people don’t even bother themselves giving that functionality to the editors. Previews functionality means rendering a specific page on demand from a CMS without publishing it in the production.&lt;/p&gt;

&lt;p&gt;If you use Gatsby, it supports the most popular CMSs, and it works seamlessly. You can either use Gatsby Cloud, and the preview server will be created automatically, and all you will have to do is just point CMS to a correct URL, or you can deploy a self-hosted version running gatsby with &lt;code&gt;GATSBY_ENABLE_REFRESH_ENDPOINT=true&lt;/code&gt;. Below is an example of how it works with Gatsby + Headless WP.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/680458460" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;With Next.js, things yet again get more complicated; &lt;a href="https://nextjs.org/docs/advanced-features/preview-mode" rel="noopener noreferrer"&gt;see the official doc&lt;/a&gt;. Again, there is a need to write it manually for every entity you plan to preview, defining rules of how the data from a preview trigger will be parsed, what will be fetched later, and what will be rendered. Instead of a five-minute setup with Gatsby, you will have to spend hours to get something usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flexibility for editors with programmatic page generation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Next.js options&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To achieve the best-in-class editor experience, editors have to be in charge of defining URLs and pages displayed on it. Let’s break a rule and start from Next.js first. There are few options to achieve or partially achieve it.&lt;/p&gt;

&lt;p&gt;1) Hardcode dynamic subpages URLs eg. &lt;code&gt;pages/post/[slug].js&lt;/code&gt;&lt;code&gt;. For example, there is a slug field for a post on the CMS side, and you agree it will always be under the&lt;/code&gt;/post`, then define &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/get-static-paths" rel="noopener noreferrer"&gt;getStaticPaths&lt;/a&gt; in the component.&lt;/p&gt;

&lt;p&gt;2) Write a wildcard component in the root &lt;code&gt;pages/[...path].js&lt;/code&gt;. Then write an additional wrapper component with the logic of mapping a specific URL to a specific component. It raises plenty of issues and increases the architecture complexity a lot.&lt;/p&gt;

&lt;p&gt;3) Use &lt;a href="https://faustjs.org/" rel="noopener noreferrer"&gt;Faust&lt;/a&gt; - a framework built based on Next.js tuned specifically for WP integration. Look at the source code, and you will find that they exactly do option 2) and see how complex it is. And it’s only available for WP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gatsby&lt;/strong&gt; &lt;strong&gt;way&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gatsby originally has been created as a SSG framework, so it has a few powerful architecture concepts &lt;/p&gt;

&lt;p&gt;1) Single point of programmatic page generation in &lt;code&gt;gatsby-node.js&lt;/code&gt;. Imagine that it’s like telling the framework in a natural language - “Please fetch all pages from a CMS, then create for each CMS page a relevant Gatsby page based on a template and make it accessible on a URL defined in a CMS.” So here, we can use different templates for pages based on the data from a CMS.&lt;/p&gt;

&lt;p&gt;2) Templates concepts. Separating Pages concept and Templates makes it easier to define hardcoded URLs and programmatically created pages based on templates.&lt;/p&gt;

&lt;p&gt;As a result, you can achieve entirely CMS-driven routes and pages creation without overwhelming yourself with complicated logic in the code.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  We demand Incremental Static Regeneration!
&lt;/h2&gt;

&lt;p&gt;Incremental Static Regeneration (ISR) allows you to update only specific pages incrementally instead of rebuilding all pages based on the incoming changes. Incremental builds are technically different from ISR, but both are trying to solve the same problem: speeding up content updates on a production by incrementally updating pages. Gatsby has been having incremental builds since v3 and actively uses it with the help of different CMS integrations and Gatsby Cloud by rebuilding only new updates, which usually takes mere seconds.&lt;/p&gt;

&lt;p&gt;Next.js had been following a bit different approach that did not require an additional build to run in case of CMS changes. Instead, it allowed you to specify the time in seconds for which the page will be fetched from a cache instead, so it will go and fetch new data when the first unlucky user comes in.&lt;/p&gt;

&lt;p&gt;Initially, I was considering putting it as a disadvantage for Next.js. It was a high-demand feature, but while I’ve been working on the article, they introduced on-demand ISR, which is supposed to solve the issue bringing the ability to revalidate cache from external sources calls. &lt;a href="https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta" rel="noopener noreferrer"&gt;As of the 17th of February, it is considered a Beta feature.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data queries everywhere
&lt;/h2&gt;

&lt;p&gt;Another highly demanding feature of Next.js is query data similar to &lt;code&gt;getServerSideProps&lt;/code&gt;, &lt;code&gt;getStaticProps&lt;/code&gt; on a component level instead of just a parent page level. Currently, you have to use props drilling or stores/context to pass data from a top-level to an underneath component. React 18 and Server Side Component should potentially solve this issue in the future.&lt;/p&gt;

&lt;p&gt;Meanwhile, in a Gatsby project, you can fetch data from a global graphql layer from anywhere using a &lt;code&gt;useStaticQuery&lt;/code&gt;. It simplifies the architecture and allows you to have reusable components.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Very external imports
&lt;/h2&gt;

&lt;p&gt;This has been brought by NextJS recently and bear all signs of being a massive advantage in perspective, for combining Low-Code / No-Code solutions with hand-written applications. To understand this feature, it is best to read &lt;a href="https://www.framer.com/docs/guides/handshake/" rel="noopener noreferrer"&gt;this great example&lt;/a&gt; of usage between Framer and Next.js. You can build a custom component in a visual editor, maybe also wrap it with some nice animations and then just import it to the project with a single line:&lt;/p&gt;

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

&lt;p&gt;This is mind-blowing and opens a lot of further possibilities. Gatsby does not allow you to do this at this moment. So if it’s something you want to use in your project, Next.js is probably a better option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sitemaps are good everywhere
&lt;/h2&gt;

&lt;p&gt;Both frameworks can handle it by using additional libraries. Gatsby handles it with a plugin and minimal configuration. For Next.js, a &lt;a href="https://www.npmjs.com/package/next-sitemap" rel="noopener noreferrer"&gt;library&lt;/a&gt; does the same but requires additional post-build script execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smartless i18n
&lt;/h2&gt;

&lt;p&gt;Interesting thing about this point that despite the fact that the implementation will look almost the same for both frameworks, another Next.js feature called &lt;a href="https://nextjs.org/docs/middleware" rel="noopener noreferrer"&gt;Global Middlewares&lt;/a&gt; makes it much more powerful. It allows you to define a top-level middleware and perform, let’s say, a country detection on an edge network and than either replacing or redirecting user to a statically generated page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We can still recommend both frameworks for website development as very reliable solutions. However, Gatsby definitely works better in terms of static generation and integrations made possible by the plugin ecosystem. For us those features are critical and we see how Gatsby allows us to build websites faster, make them flexible and maintainable. Next.js is a more dynamic-oriented framework, and I would go for it only if I need an additional auth layer on the website or maybe in case when I need that Global Middleware functionality that could help deal with i18n, A/B testing, or Feature Flags. These are definitely nice to have, but usually, they are only required for a huge enterprise-grade tech where you can afford such experiments.&lt;/p&gt;




&lt;p&gt;Want to hear more about Gatsby, Next.js, and tips for building high-performing and visually stunning websites? &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;Follow me on Twitter.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Measuring Gatsby projects build time using paid plans of popular static website hosting platforms</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Tue, 01 Feb 2022 11:22:03 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/measuring-gatsby-projects-build-time-using-paid-plans-of-popular-static-website-hosting-platforms-47pp</link>
      <guid>https://forem.com/alex_barashkov/measuring-gatsby-projects-build-time-using-paid-plans-of-popular-static-website-hosting-platforms-47pp</guid>
      <description>&lt;p&gt;Jamstack is showing rapid growth these days. We get more and more tools and frameworks for it almost every month, bringing a new life for the concept of static sites generation.&lt;/p&gt;

&lt;p&gt;Our go-to framework for building websites at &lt;a href="https://pixelpoint.io/" rel="noopener noreferrer"&gt;Pixel Point&lt;/a&gt; was always Gatsby. Three years ago, when we’ve just started to use it, there were not too many options where you could deploy the site - &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; or &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;. Now things have changed, you also have &lt;a href="https://www.gatsbyjs.com/products/cloud/" rel="noopener noreferrer"&gt;Gatsby Cloud&lt;/a&gt;, &lt;a href="https://pages.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare pages&lt;/a&gt;, &lt;a href="https://aws.amazon.com/amplify/" rel="noopener noreferrer"&gt;AWS Amplify&lt;/a&gt;, and you can even make similar build tools within your infrastructure using projects like &lt;a href="https://www.waypointproject.io/" rel="noopener noreferrer"&gt;Waypoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Having a statically generated site comes with one significant drawback - build time. If you have a small blog or single-page website, all hosting or frameworks will give pretty much the same results, and the difference won’t be noticeable, but things get worse when it comes to 100+ pages. On that scale, the platform could make a massive difference in terms of build performance for developers, designers, and marketers, since no one wants to wait hours to see the changes live.&lt;/p&gt;

&lt;p&gt;So I decided to run tests across the most popular platforms using paid plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Platforms&lt;/li&gt;
&lt;li&gt;Test cases&lt;/li&gt;
&lt;li&gt;Markdown-based website&lt;/li&gt;
&lt;li&gt;Headless WordPress website&lt;/li&gt;
&lt;li&gt;Winners&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Platforms
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Netlify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pro plan - $20/month per member.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pro plan - $20/month per member.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gatsby Cloud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pro plan - $50/month, 2 seats included, $20 per additional member.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pro plan - $20/month, unlimited seats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Amplify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First 1000 minutes free, then 0.01$ per build minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-hosted&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$50/month, a self-hosted server running Drone CI with the following specs: AMD Ryzen 5 3600 Hexa-Core "Matisse" (Zen2), 64 RAM, 512GB Raid NVME SSD. Unlimited seats. Deployment to Netlify using netlify-cli.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test cases
&lt;/h2&gt;

&lt;p&gt;I started by testing clean cache build-time performance. Although you usually have a cache that works well and reduces the build time, there are still some cases when you have to clean it up manually, or Gatsby will clean it for you on changes in gatsby-node.js, gatsby-config.js, or package.json files. &lt;/p&gt;

&lt;p&gt;I made five runs for each test and then wrote down the average. I also used a stopwatch to ensure that there is no significant difference between the actual notification about completion and the number in UI. The goal of the stopwatch was not to measure it accurately but to figure out whether platforms show us the truth and don’t deduct from a build time some stages such as environment initialization or publication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown-based website
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clean cache build time
&lt;/h3&gt;

&lt;p&gt;First in the test was a markdown-based website. The results were quite surprising. Gatsby Cloud did the job within two min., which is eight times faster than Netlify - 16. The results for Gatsby Cloud are quite similar to what I see running builds locally on Apple M1 chips, which is insane. I’m sure they do some nice magic behind the scenes, at least parallel image optimization.&lt;/p&gt;

&lt;p&gt;Vercel was a little faster than Cloudflare pages finishing with 10:30 vs. 11:23. I definitely underestimated AWS Amplify and thought they would run builds on free tier EC2 instances, but they are running builds on a host with 4 vCPU, 7GB memory (appreciate that transparency in UI), so it finished as 9:47.&lt;/p&gt;

&lt;p&gt;The self-hosted solution was relatively fast and very close in terms of the build time to Gatsby Cloud, but uploading results to Netlify takes around half of that time, so it finished second place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjjnycyiid81t2ntbnajw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjjnycyiid81t2ntbnajw.png" alt="Clean cache build time (seconds, lower is better)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The funny thing here is that Gatsby Cloud could complete the whole build in the amount of time it took Cloudflare to initialise the environment.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvd28h06bv0dpyk8i1nrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvd28h06bv0dpyk8i1nrx.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Preview build time
&lt;/h3&gt;

&lt;p&gt;Here I’ve started to feel that it will be a death race since competitors began to reveal their problems and how differently they handle different scenarios. In the Deploy Preview test, I created a separate branch from the master and made two changes in a jsx file and another in md file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykljznlqbqirof5wt8r2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykljznlqbqirof5wt8r2.png" alt="Deploy preview build time (minutes, lower is better)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Netlify(16:16), Cloudflare(11:16) and AWS Amplify(9:14) showed the slowest results, and here is why. Netlify has a nice plugin that persists cache between builds, BUT it does not work if your build lasts for more than 15 minutes. &lt;/p&gt;

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

&lt;p&gt;Cloudflare Pages does not have an option to persist cache between builds, so it shows slow results every time. &lt;/p&gt;

&lt;p&gt;AWS Amplify has an option to persist the cache, but all their &lt;a href="https://docs.amplify.aws/guides/hosting/gatsby/q/platform/js/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; don’t have information about how to make it work. Simply adding necessary folders to their build yaml file does not &lt;a href="http://work.So" rel="noopener noreferrer"&gt;work.&lt;/a&gt; After spending a reasonable amount of time researching it, I passed on it since I’m pretty sure their users won’t dive into that research either. Without that configuration, the only thing that persists between builds is node_modules.&lt;/p&gt;

&lt;p&gt;Vercel did a great job and finished within 3:32. I was impressed with this result because all cache magic is done behind the scene, and there is no need to install additional plugins for gatsby to the platform and inside the code to make it work properly.&lt;/p&gt;

&lt;p&gt;Gatsby Cloud was again a winner with 0:49, and the self-hosted version was just 25 seconds behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warm cache build time
&lt;/h3&gt;

&lt;p&gt;By the warm cache build time, I mean a subsequent build, that runs from the same branch where the build happened before. It’s a case you have every time you redeploy a website on content change or make additional commits to your Pull Request.&lt;/p&gt;

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

&lt;p&gt;Vercel(1:08) and Selfhosted(1:06) finished very close, but Gatsby Cloud was faster with 0:27.&lt;/p&gt;

&lt;p&gt;Cloudflare Pages and Netlify, AWS Netlify however, have room for improvement for sure. &lt;/p&gt;

&lt;h2&gt;
  
  
  Headless WordPress website
&lt;/h2&gt;

&lt;p&gt;Then I jumped testing Gatsby v4 connected with the WordPress website. The site has two languages, a batch of common WP plugins, and around 1000 pages in total. This is where things get challenging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean cache build time
&lt;/h3&gt;

&lt;p&gt;Netlify even has not finished the build and stopped after 22 minutes. Vercel and Cloudflare pages completed within 24 minutes. Gatsby Cloud was fast, but it was tough even for it - 17:47. AWS Amplify surprised the most, showing consistent time around 15 min. The self-hosted CI achieved the quickest result - 11:30. The server we host is probably closer to the WP hosting, and it took less time for initial assets to download.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwi657g72zefszq6cfiex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwi657g72zefszq6cfiex.png" alt="Clean cache run build time (minutes, lower is better)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Preview build time
&lt;/h3&gt;

&lt;p&gt;I guess we can consider that Cloudflare, Netlify, AWS Amplify failed this round, so let’s go straight to Vercel. It took 2:39 for Deploy Preview, which is a very nice result. Gatsby Cloud did the same job within 1:45, and the Self-hosted version handled it within 1:23.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsim8pb9p60zwiv68wqgp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsim8pb9p60zwiv68wqgp.png" alt="Deploy preview build time (minutes, lower is better)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Warm cache build time
&lt;/h3&gt;

&lt;p&gt;Vercel did not show any difference from Deploy Preview. For Gatsby Cloud, however, it took 20 seconds less. The self-hosted version had the same excellent results. But I have to mention there was a moment that uploading files over Netlify CLI with a self-hosted version took more than 6 minutes. I decided not to include it because it happened only once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgih72oziws31wayloem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgih72oziws31wayloem.png" alt="Warm cache build time (minutes, lower is better)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus
&lt;/h3&gt;

&lt;p&gt;It won’t be complete without showing you one more thing. All tests above were made, making changes in one jsx file and one blog post. But here is what Gatsby Cloud can do if you make a content change(from CMS like WP/Contentful/Prismic etc), which happens the most. &lt;/p&gt;

&lt;p&gt;From an architecture perspective, based on build logs, it feels like Gatsby Cloud keeps the latest instance of your production version in a sleep mode, then they can wake it up quickly and skip some build steps, and knowing a content diff produce a new build very quickly.&lt;/p&gt;

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

&lt;p&gt;It took just 20 seconds from making a change in a CMS to seeing it live in production! That’s very impressive. A few years ago, when &lt;a href="https://twitter.com/kylemathews" rel="noopener noreferrer"&gt;Kyle Mathews&lt;/a&gt; (Gatsby creator) said that they were going to make it possible to rebuild the site in a matter of seconds, I was really skeptical about this, but now it seems it could be true. Great job!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This result only could be achieved if you change a post or page. If you change shared data such as Menus, it will have to rebuild each page so that it will take around 50 seconds in the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Winners
&lt;/h2&gt;

&lt;p&gt;Gatsby Cloud won the race showing the best in class performance build time. I’m sure it uses the most powerful cars compared to other platforms and does some background magic. Vercel showed up still as a strong competitor. It did not show great results in cold runs, but cache changes everything. Talking with our clients, we mentioned that nobody cares when builds take less than 10 minutes, but after passing that threshold, you start to receive questions about how to make it faster. So considering this, I definitely could recommend both Gatsby and Vercel solutions.&lt;/p&gt;

&lt;p&gt;Despite all the love that I have and share about Cloudflare products, Pages feels like the weakest one in their lineup. Development has been very slow, and I did not mention significant changes since the first beta and the current state. In my perspective, Cloudflare has the most affordable and reasonable pricing plan with no seat limits and bandwidth limitations. So let’s keep watching.&lt;/p&gt;

&lt;p&gt;AWS Amplify... What can I say? It’s AWS. If you love their services and don’t find their UX the worst, it could still be an option. Build configuration as YAML files, separate settings to enable Deploy previews and builds for other branches.  Missing Cache-Control policies out of the box. All of these don’t play well in their favor. &lt;/p&gt;

&lt;p&gt;Netlify, I suppose, is still the most popular and very affordable solution on the market. Great UI and amazing features set, but build time could become a severe bottleneck if your site scales faster.&lt;/p&gt;

&lt;p&gt;A self-hosted alternative is fast, does not have any limits, and costs just $50/m for a very powerful machine; however, you need to keep in mind that you will probably need to spend hours setting it up at least once in a while.&lt;/p&gt;




&lt;p&gt;Want to hear more about Gatsby, Next.js, and tips for building high-performing and visually stunning websites? &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;Follow me on Twitter.&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://github.com/vshn/website" rel="noopener noreferrer"&gt;Headless WordPress website source code&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/cilium/cilium.io" rel="noopener noreferrer"&gt;Markdown based website source code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>How to hide Feature A and show Feature B for different users in React</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Tue, 20 Jul 2021 15:16:18 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/how-to-hide-feature-a-and-show-feature-b-for-different-users-in-react-j6e</link>
      <guid>https://forem.com/alex_barashkov/how-to-hide-feature-a-and-show-feature-b-for-different-users-in-react-j6e</guid>
      <description>&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;Have you ever wondered how do companies like Slack, Netlify, Zoom, Facebook (and rest of FAANG for sure)  gradually roll out new features just for some users?  🤔  &lt;/p&gt;

&lt;p&gt;Wonder no more! It is done via Feature Flags (or FF for short) and we're just about to dive into this concept.&lt;/p&gt;

&lt;p&gt;Feature Flags services work the following way. You define a list of features(eg. dark mode, horizontal login layout, design 2.0) and assign it to a group of users specifying conditions whether it should be enabled or disabled.&lt;/p&gt;

&lt;p&gt;Chances are that you've already used FF in your projects but wasn't aware of this. Did you store in a database some booleans that indicate whether particular user should have an access to a certain feature? If yes, then congratulations - you indeed have experience working with feature flags.&lt;/p&gt;

&lt;p&gt;Booleans in DB were good up to a certain point, but progress never stops and now we have much more flexible and dev friendly way to control feature presence. Let's take a look at feature flags services.  FF services have the following bonuses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI where you can define and manage features&lt;/li&gt;
&lt;li&gt;Users segmentation&lt;/li&gt;
&lt;li&gt;A/B testing&lt;/li&gt;
&lt;li&gt;Flags analytics&lt;/li&gt;
&lt;li&gt;Staged feature rollouts&lt;/li&gt;
&lt;li&gt;SDKs for different languages/frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we are going to use &lt;a href="https://github.com/Flagsmith/flagsmith" rel="noopener noreferrer"&gt;Flagsmith&lt;/a&gt; - open source feature flag and remote config service, but you can check commercial alternatives such as &lt;a href="https://launchdarkly.com/" rel="noopener noreferrer"&gt;LaunchDarkly&lt;/a&gt;. Our goal is to learn how to start using feature flags in React and make the process of launching new functionality for users smoother.&lt;/p&gt;

&lt;p&gt;FF manipulation with Flagsmith can be done pretty much anywhere (frontend/backend/mobile platform). Let's take a look at the following schema: &lt;/p&gt;

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

&lt;p&gt;Let's break down what is happening here.&lt;/p&gt;

&lt;p&gt;1) User requests the page&lt;br&gt;
2) A certain side of your app (FE/BE) makes a call to Flagsmith service with user uuid&lt;br&gt;
3) Flagsmith matches received uuid with the configuration of available features for this particular user and returns the info on that&lt;br&gt;
4) App generates a page based on received feature info&lt;/p&gt;

&lt;p&gt;Definitely not a rocket science. Now to practice! &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Flagsmith" rel="noopener noreferrer"&gt;
        Flagsmith
      &lt;/a&gt; / &lt;a href="https://github.com/Flagsmith/flagsmith" rel="noopener noreferrer"&gt;
        flagsmith
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open Source Feature Flagging and Remote Config Service. Host on-prem or use our hosted version at https://flagsmith.com/
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Our goal
&lt;/h3&gt;

&lt;p&gt;We want to build pretty much standard dashboard app (sorry, no todos today) with authorization set up. We want to show certain feature components only to those users who opted-in for beta. &lt;/p&gt;

&lt;p&gt;React, Next.js, Next-Auth and beautiful dashboard components from Tailwind UI are going to help us building it.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Pre-requirements
&lt;/h3&gt;

&lt;p&gt;Create a new app with Next.js, React and Next-Auth or clone this &lt;a href="https://github.com/Flagsmith/flagsmith-js-client/tree/main/examples/nextjs-auth" rel="noopener noreferrer"&gt;sample repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additional links&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/docs/getting-started" rel="noopener noreferrer"&gt;Getting started with Next.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://next-auth.js.org/getting-started/introduction" rel="noopener noreferrer"&gt;Getting started with nextauth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flagsmith.com/" rel="noopener noreferrer"&gt;Then create an account&lt;/a&gt; in the cloud version of Flagsmith.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Configure Flagsmith environment
&lt;/h3&gt;

&lt;p&gt;The configuration process is simple. Once you've signed up, create a new project. In Flagsmith you can define multiple environments per project, each environment keeps its own features/users/segments and API key.&lt;/p&gt;

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

&lt;p&gt;Let's create our first feature, we will name it - "pinned_projects". Beta users will be able to pin projects and see them.&lt;/p&gt;

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

&lt;p&gt;Next step we need to install &lt;code&gt;flagsmith-react&lt;/code&gt; package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;flagsmith&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As an initial step, we will wrap our _app.js with &lt;code&gt;FlagsmithProvider&lt;/code&gt; and pass Flagsmith API key, which we can get from the Settings page in Flagsmith UI. FlagsmithProvider will allow to use &lt;code&gt;useFlagsmith&lt;/code&gt; hook and pass the state to underlying components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;components/auth&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;Provider&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SessionProvider&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="s1"&gt;next-auth/client&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;FlagsmithProvider&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="s1"&gt;flagsmith-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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styles/globals.css&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;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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FlagsmithProvider&lt;/span&gt; &lt;span class="na"&gt;environmentId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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_FLAGSMITH_API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SessionProvider&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&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="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SessionProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FlagsmithProvider&lt;/span&gt;&lt;span class="p"&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;code&gt;FlagsmithProvider&lt;/code&gt; will initialise Flagsmith javascript client under the hood and you will be able to start getting flags declared in the UI. But it does not make too much sense unless we get user-specific flags. To let Flagsmith know which user requests flags, we need to &lt;code&gt;identify&lt;/code&gt; him. The perfect place for this Auth component, that responsible for checking user session and redirecting users to the sign-in page in case it's expired.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signIn&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="s1"&gt;next-auth/client&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;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="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFlagsmith&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="s1"&gt;flagsmith-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;Auth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isIdentified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getTrait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTrait&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlagsmith&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSession&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;session&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="c1"&gt;// Identify user and set email trait if does not exist&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;identifyUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&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;await&lt;/span&gt; &lt;span class="nf"&gt;identify&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nf"&gt;getTrait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasEmail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setTrait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&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="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&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="c1"&gt;// Do nothing while loading&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;isUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// If not authenticated, force log in&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&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="o"&gt;=&amp;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;isUser&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isIdentified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// In the example we don't save users in the database so we don't have id that should be used for identification&lt;/span&gt;
      &lt;span class="c1"&gt;// Instead we're going to use email as a trait and id&lt;/span&gt;
      &lt;span class="nf"&gt;identifyUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isIdentified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isUser&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;isUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Session is being fetched, or no user.&lt;/span&gt;
  &lt;span class="c1"&gt;// If no user, useEffect() will redirect.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can see that we use &lt;code&gt;getTrait&lt;/code&gt; and &lt;code&gt;setTrait&lt;/code&gt;. Traits are simply key/value pairs that are associated with individual Identities. You can pass any additional information to the user, that later could be used for segmentation eg. current plan, opt-in for beta features or newsletters, etc. &lt;/p&gt;

&lt;p&gt;Let's create first segment and call it &lt;code&gt;beta_opt_in&lt;/code&gt; . Go to Segments → Create Segment. Here you also could define an expression based on the trait which will add users that match that condition to the segment. We can start from a simple one, such as if email has &lt;code&gt;[pixelpoint.io](http://pixelpoint.io)&lt;/code&gt; consider them as opted-in for beta features.&lt;/p&gt;

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

&lt;p&gt;The final step is to add a check to our React component using &lt;code&gt;hasFeature("pinned_projects")&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hasFeature&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFlagsmith&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 relative z-0 overflow-y-auto focus:outline-none"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeHeader&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;hasFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pinned_projects&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-4 mt-6 sm:px-6 lg:px-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PinnedProjects&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pinnedProjects&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProjectsTable&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Here you go!
&lt;/h3&gt;

&lt;p&gt;If you've been following along, by this time you should have a nice little pet project demonstrating a modern approach to feature flags. Congratulations🎉&lt;/p&gt;

&lt;p&gt;Don't hesitate to extend it or leverage the idea in your existing project.&lt;/p&gt;

&lt;p&gt;P.S. Further reading&lt;/p&gt;

&lt;p&gt;In this article we just took a quick glance at Feature Flags and built a simple example app, but FF system is capable of much more. Check out: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.flagsmith.com/advanced-use/ab-testing" rel="noopener noreferrer"&gt;A/B testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flagsmith.com/advanced-use/flag-analytics" rel="noopener noreferrer"&gt;Feature flags analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flagsmith.com/advanced-use/staged-feature-rollouts" rel="noopener noreferrer"&gt;Staged rollouts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>react</category>
    </item>
    <item>
      <title>Top 10 open-source SaaS that could help you build the next big thing </title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Thu, 15 Jul 2021 08:48:03 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/top-10-open-source-saas-that-could-help-you-build-the-next-big-thing-2eai</link>
      <guid>https://forem.com/alex_barashkov/top-10-open-source-saas-that-could-help-you-build-the-next-big-thing-2eai</guid>
      <description>&lt;p&gt;I'm super happy that investors and big companies are finally happy to support Open Source projects. It was hard to imagine 10 years ago that there will be plenty of SaaS built based on open-source model.&lt;/p&gt;

&lt;p&gt;Here is the list of my favourites that could help you build amazing modern architecture covering different aspects of product development and architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hasura
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/hasura" rel="noopener noreferrer"&gt;
        hasura
      &lt;/a&gt; / &lt;a href="https://github.com/hasura/graphql-engine" rel="noopener noreferrer"&gt;
        graphql-engine
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blazing fast, instant realtime GraphQL APIs on your DB with fine grained access control, also trigger webhooks on database events.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Hasura simplifies the development a lot. How do you usually build your apps? Probably you choose some framework, set up ORM, set up DB, write manually migrations, then you build batch of CRUD endpoints either with REST or Graphql, add authorization layer. &lt;/p&gt;

&lt;p&gt;With Hasura you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easily create most of your tables for PostgreSQL, TimescaleDB, MySQL, MongoDB, MSSQL using nice UI&lt;/li&gt;
&lt;li&gt;every DB structure modification will be recorded as a migration(so you can easily run it within your CI by using Hasura CLI)&lt;/li&gt;
&lt;li&gt;each table will automatically get relevant graphql/rest endpoints&lt;/li&gt;
&lt;li&gt;protect each endpoint by a powerful set of rules for each user role&lt;/li&gt;
&lt;li&gt;and if you need more business logic you can merge generated endpoints with custom graphql server&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TimescaleDB
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/timescale" rel="noopener noreferrer"&gt;
        timescale
      &lt;/a&gt; / &lt;a href="https://github.com/timescale/timescaledb" rel="noopener noreferrer"&gt;
        timescaledb
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An open-source time-series SQL database optimized for fast ingest and complex queries.  Packaged as a PostgreSQL extension.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;An extension for PostgreSQL that boost the performance of time-series operations. Super simple to use, amazing documentation and nice cloud service. You can always use it as a replacement of PostgreSQL because it does not have any downsides, but in case you start saving some metrics, stats to your database you will be covered. It has 10x-100x faster queries than PostgreSQL for time-series.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supertokens
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/supertokens" rel="noopener noreferrer"&gt;
        supertokens
      &lt;/a&gt; / &lt;a href="https://github.com/supertokens/supertokens-core" rel="noopener noreferrer"&gt;
        supertokens-core
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open source alternative to Auth0 / Firebase Auth / AWS Cognito 
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Every project needs authorization. If you used Auth0, it's pretty much the same but it's Open Source, so you can easily host it within your own infrastructure or either use cloud version. It works based on JWT, so simply integrates with Hasura.&lt;/p&gt;

&lt;h3&gt;
  
  
  n8n
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/n8n-io" rel="noopener noreferrer"&gt;
        n8n-io
      &lt;/a&gt; / &lt;a href="https://github.com/n8n-io/n8n" rel="noopener noreferrer"&gt;
        n8n
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free and source-available fair-code licensed workflow automation tool. Easily automate tasks across different services.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Don't want to bother yourself with programming automation between different services and spending hours on that? Try n8n. Powerful UI to design different integrations and automations that rely on data changes/triggers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rancher
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rancher" rel="noopener noreferrer"&gt;
        rancher
      &lt;/a&gt; / &lt;a href="https://github.com/rancher/rancher" rel="noopener noreferrer"&gt;
        rancher
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Complete container management platform
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Modern applications deserve modern infrastructure and managing it could be a painful process. Rancher simplifies management of Kubernetes clusters whether you run it in the cloud or on bare-metal servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drone CI
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/harness" rel="noopener noreferrer"&gt;
        harness
      &lt;/a&gt; / &lt;a href="https://github.com/harness/harness" rel="noopener noreferrer"&gt;
        harness
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Harness Open Source is an end-to-end developer platform with Source Control Management, CI/CD Pipelines, Hosted Developer Environments, and Artifact Registries.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Despite Github actions and Gitlab are very popular solutions, Drone CI has fantastic architecture and a very simple set-up. After it's been acquired by Harness, it's got a major 2.0.0 update that brought a lot of cool features such as activity charts, user management, new UI. Drone CI is a very powerful and flexible tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grafana
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/grafana" rel="noopener noreferrer"&gt;
        grafana
      &lt;/a&gt; / &lt;a href="https://github.com/grafana/grafana" rel="noopener noreferrer"&gt;
        grafana
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more. 
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;You can't build the next big thing without having a proper monitoring analytics solution. Grafana was an essential tool in that space for a very long time, and now they also have a cloud version. &lt;/p&gt;

&lt;h3&gt;
  
  
  K6
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/grafana" rel="noopener noreferrer"&gt;
        grafana
      &lt;/a&gt; / &lt;a href="https://github.com/grafana/k6" rel="noopener noreferrer"&gt;
        k6
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A modern load testing tool, using Go and JavaScript - https://k6.io
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;The best performance testing tool and it's open source. Recently K6 has been acquired by Grafana Labs, so I'm sure it will become even better. If you had experience writing scripts for jMeter, K6 experience will be 100x times better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sentry
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/getsentry" rel="noopener noreferrer"&gt;
        getsentry
      &lt;/a&gt; / &lt;a href="https://github.com/getsentry/sentry" rel="noopener noreferrer"&gt;
        sentry
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Developer-first error tracking and performance monitoring
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Error reporting is crucial when you want to handle customer issues before it will have a negative impact on your business. Sentry allows you to simply integrate their SDK in your framework/language and start to receive alerts when errors happen either on the client or server-side. &lt;/p&gt;

&lt;h3&gt;
  
  
  Flagsmith
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Flagsmith" rel="noopener noreferrer"&gt;
        Flagsmith
      &lt;/a&gt; / &lt;a href="https://github.com/Flagsmith/flagsmith" rel="noopener noreferrer"&gt;
        flagsmith
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open Source Feature Flagging and Remote Config Service. Host on-prem or use our hosted version at https://flagsmith.com/
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;In 2021 you should stop pushing major updates to all users at the same time. Control their expectations and react to issues predictively by using feature flags. Simply integrate it with your framework by using SDK, then manage who should be allowed to have access to certain features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Building projects with open source technologies is amazing, since you don't need to spend a fortune to test things, but as projects grow you always could try paid cloud versions.&lt;/p&gt;

&lt;p&gt;What open source SaaS do you use in your project?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Speed up your development with this new VS Code extension</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Fri, 19 Jun 2020 15:03:16 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/speed-up-your-development-with-this-new-vs-code-extension-5b3m</link>
      <guid>https://forem.com/alex_barashkov/speed-up-your-development-with-this-new-vs-code-extension-5b3m</guid>
      <description>&lt;p&gt;My friend and I recently developed VS Code extension – &lt;a href="https://github.com/snipsnapdev/snipsnap" rel="noopener noreferrer"&gt;Snipsnap&lt;/a&gt; that aims to solve the existing problem with code snippets collections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/snipsnapdev/snipsnap" rel="noopener noreferrer"&gt;Snipsnap&lt;/a&gt; is the ultimate snippets collection and VS Code extension that automatically exposes all available snippets for every library you are using in your project.&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%2Fuser-images.githubusercontent.com%2F2697570%2F73568644-23bc0180-4469-11ea-8b64-843c7a9a92d2.gif" 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%2Fuser-images.githubusercontent.com%2F2697570%2F73568644-23bc0180-4469-11ea-8b64-843c7a9a92d2.gif" alt="out"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We already have snippets for React, Redux, Gatsby, Next.js, Vue. The full list of snippets you can find &lt;a href="https://github.com/snipsnapdev/snipsnap/tree/master/snippets/javascript" rel="noopener noreferrer"&gt;there&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔥 What problem Snipsnap is trying to solve?
&lt;/h2&gt;

&lt;p&gt;Problem #1&lt;/p&gt;

&lt;p&gt;Almost every popular language has a lot of different libraries that people used to use. Some of them big, some are small. For each library, you should keep in mind a lot of different syntax constructions in order to use them. Code snippets help to fix it, but you don't want to create and you will not install extensions for each small library. Instead of it we want to have a single Snipsnap extension that will fetch relevant code snippets based on languages, packages you use in your current project.&lt;/p&gt;

&lt;p&gt;Problem #2&lt;/p&gt;

&lt;p&gt;Different snippets extensions follow different rules and use unpredictable shortcuts such as "rccp", "ecrp", 'impp' etc. Having those unreadable shortcuts don't allow you to actually search across all snippets you have for a specific case. We want to change it by standardizing snippets format and providing clean, predictable search syntax such as library-name keyword, so you can always type the name of your library and get a full list of snippets available for it.&lt;/p&gt;

&lt;p&gt;Problem #3&lt;/p&gt;

&lt;p&gt;Each IDE has individual snippets format that does not compatible with other IDEs. So having independent snippets format could allow us to create Snipsnap extensions for each popular IDEs and using converters transform snippets from one format to another.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ How it works
&lt;/h2&gt;

&lt;p&gt;Snipsnap VS Code extension scans your package.json(or yarn.lock) and searches on the server available snippets for packages you have in the project. It means that you don't need anymore install different extensions with snippets for frameworks, libraries you use.&lt;/p&gt;

&lt;p&gt;Snipsnap extension creates snipsnap.code-snippets inside .vscode folder with all snippets, so snippets will be available even for other developers who did not install extension.&lt;/p&gt;

&lt;p&gt;Snipsnap scans for newly available snippets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on folder opening&lt;/li&gt;
&lt;li&gt;on pressing command "Snipsnap: Feth the snippets" via the command palette
All snippets currently present in this repository and follow the guidelines described below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🗓️ Our plans and vision
&lt;/h2&gt;

&lt;p&gt;The current version could be called MVP and it's there just for the one purpose – test the idea and get first feedbacks. If you like extension - star the repository, tell us about your experience or help us to improve the project.&lt;/p&gt;

&lt;p&gt;We don't want to stop just on having snippets for Javascript. We want to make it standard for all popular languages and their package managers. So cover Ruby, Go, Python, PHP libraries also in our plans.&lt;/p&gt;

&lt;p&gt;We believe that snippets could become a perfect solution for providing simple documentation and examples. Code snippets should become the part of packages repositories like README files. You build your library, you put snippets together with it in .snipsnap.json file and then we fetch it.&lt;/p&gt;

&lt;p&gt;Having standardized collection could allow us to write extensions and converters for all popular IDE's, that will finally make code snippets independent from IDE. Let's say in a couple of years the new awesome IDE will be released, instead of writing a whole batch of snippets for new IDE you will be able to just continue using Snipsnap and the collection you already created.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Your feedback matters
&lt;/h2&gt;

&lt;p&gt;We are very curious to know what do you think about Snipsnap. Let us know by leaving a comment or submitting an issue on &lt;a href="https://github.com/snipsnapdev/snipsnap" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>opensource</category>
      <category>vscode</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A simple guide for port forwarding Kubernetes resources</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Mon, 12 Aug 2019 10:25:55 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/a-simple-guide-for-port-forwarding-kubernetes-resources-4d3j</link>
      <guid>https://forem.com/alex_barashkov/a-simple-guide-for-port-forwarding-kubernetes-resources-4d3j</guid>
      <description>&lt;p&gt;Kubernetes changes the way how people deploy, orchestrate, and build their infrastructures. In this article, we will learn the simplest way of accessing your internal Kubernetes resources from your local machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  💼 Use case
&lt;/h2&gt;

&lt;p&gt;You deployed PostgreSQL to your cluster, and you don't want to expose it publicly, but want to make some queries and access DB.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Solution
&lt;/h2&gt;

&lt;p&gt;Use Kubernetes native port-forwarding functionality. Specific Kubernetes API allows creating a tunnel from your cluster resource to your localhost over a single HTTP connection. As a result, you can access at your localhost 5432 port PostgreSQL from your Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;You could do it with &lt;strong&gt;kubectl&lt;/strong&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward -n default deployment/postgres 8432:5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or you could use very simple open-source GUI – &lt;strong&gt;&lt;a href="https://kube-forwarder.pixelpoint.io/" rel="noopener noreferrer"&gt;Kube Forwarder&lt;/a&gt;&lt;/strong&gt;. Having CLI tool is excellent, but it does not have a few essential features&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-reconnect&lt;/strong&gt;(if your connection is not so stable, you will have manually rerun the command)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-complete&lt;/strong&gt;(you always need to keep in mind names of your services, namespaces, I had some hints in notes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple cluster support&lt;/strong&gt; (switch context each time you want to forward something is not a great experience as well)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So keeping that in mind we developed &lt;a href="https://kube-forwarder.pixelpoint.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kube Forwarder – easy to use Kubernetes port-forwarding manager&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa6cppndcafdm7a1e3004.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa6cppndcafdm7a1e3004.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other important features we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-import&lt;/strong&gt; of kube config clusters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import/Export&lt;/strong&gt; all bookmarked clusters and resources(works well for sharing it with the team)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward multiple services&lt;/strong&gt; at the same time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero native dependencies&lt;/strong&gt; (works without kubectl CLI)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to forward resources with Kube Forwarder❓&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It's very simple three steps process if you do it first time –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a cluster configuration&lt;/li&gt;
&lt;li&gt;Find resource you want to forward&lt;/li&gt;
&lt;li&gt;Start forwarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Add a cluster configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you already have kubeconfig file, Kube Forwarder will suggest you to import clusters you have:&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3eca25uneumkv34sze37.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3eca25uneumkv34sze37.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also have other options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add clusters by choosing one or multiple files&lt;/li&gt;
&lt;li&gt;Paste your config as a plain text. It could be useful if you created a new cluster and want to get access to some deployments&lt;/li&gt;
&lt;li&gt;Restore the configuration from Kube Forwarder JSON. Useful when you as a devops want to prepare easy to use forwarding for your dev team who are not well familiar with Kubernetes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Find the resource you want to forward&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvkm7ovita93vj5e1vsyd.gif" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvkm7ovita93vj5e1vsyd.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kube Forwarder has autocomplete by namespaces, pods, deployments, so it makes the process of adding a new resource for forwarding very simple.&lt;/p&gt;

&lt;p&gt;Set as a local port - port from your local machine and resource port – the port where your app launched. Other fields I think don't need extra clarification.&lt;br&gt;
Once you added the resource - press "Play" button and access your service from your local machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙏 We're looking for your feedback
&lt;/h2&gt;

&lt;p&gt;We believe that apps like this could make you more productive and attract more people to  start using Kubernetes. Let us know what do you think about the app and your ideas how could we make it better.&lt;/p&gt;

&lt;p&gt;We built the app with Electron, so it works well on macOS, Windows and Linux.&lt;/p&gt;

&lt;p&gt;Github - ⭐ &lt;a href="https://github.com/pixel-point/kube-forwarder" rel="noopener noreferrer"&gt;https://github.com/pixel-point/kube-forwarder&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Automate NPM packages security fixes with recurring tasks on CI</title>
      <dc:creator>Alex Barashkov</dc:creator>
      <pubDate>Thu, 09 May 2019 09:42:59 +0000</pubDate>
      <link>https://forem.com/alex_barashkov/automate-npm-packages-security-fixes-with-recurring-tasks-on-ci-pi1</link>
      <guid>https://forem.com/alex_barashkov/automate-npm-packages-security-fixes-with-recurring-tasks-on-ci-pi1</guid>
      <description>&lt;p&gt;When developing on Node.js, our &lt;a href="https://pixelpoint.io/" rel="noopener noreferrer"&gt;team&lt;/a&gt; uses a lot open source NPM packages. Each of them has their own benefits and drawbacks that they bring to your project. In this article, we will discuss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost-free options for vulnerability testing NPM dependencies&lt;/li&gt;
&lt;li&gt;Drone CI configuration for running recurring checks&lt;/li&gt;
&lt;li&gt;Auto Pull Request creation with fixed packages&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NPM audit and more
&lt;/h3&gt;

&lt;p&gt;The first thing that comes to mind when we talk about vulnerability audits is the NPM audit tool. This tool uses a publicly available vulnerability catalog to check your project and propose library version updates to fix any issues discovered. You can read more in the official &lt;a href="https://blog.npmjs.org/post/173719309445/npm-audit-identify-and-fix-insecure" rel="noopener noreferrer"&gt;NPM blog here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another good, free report that still uses out-of-the-box available options is &lt;strong&gt;npm outdated&lt;/strong&gt;. This report uses a command check registry to see if any installed packages are currently outdated. That information is not necessarily useful for day-to-day work, but good to know for the long term, so you’re less tempted to simply abandon a project.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ npm outdated
Package      Current   Wanted   Latest  Location
glob          5.0.15   5.0.15    6.0.1  test-outdated-output
nothingness    0.0.3      git      git  test-outdated-output
npm            3.5.1    3.5.2    3.5.1  test-outdated-output
local-dev      0.0.3   linked   linked  test-outdated-output
once           1.3.2    1.3.3    1.3.3  test-outdated-output


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Automated npm outdated reports
&lt;/h3&gt;

&lt;p&gt;These tools are very useful, but, of course, automated reports are even better. For this purpose, we use &lt;a href="https://drone.io/" rel="noopener noreferrer"&gt;Drone CI&lt;/a&gt;(free and open source) and the new feature, Cron Jobs, to set recurring tasks. You’re free to use any other CI you like, however, which will probably support the same functionality. For those not familiar with Drone CI, read my &lt;a href="https://dev.to/alex_barashkov/getting-started-with-open-source-drone-ci-4pgc"&gt;Getting Started article here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Since Drone CI supports multiple pipelines, each report has its own pipeline and does not affect the main one. For a wider look, check out the example &lt;a href="//example.com"&gt;here&lt;/a&gt;. In the meantime, let’s begin with npm outdated. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

kind: pipeline
name: npm outdated

steps:
- name: outdated
  image: node:10-alpine
  commands:
    - npm outdated

- name: slack_notification
  image: plugins/slack
  settings:
    webhook: https://hooks.slack.com/services/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
    template: &amp;gt;
      NPN detected outdated packages at *{{repo.name}}* for *{{build.branch}}* branch. 
      Report available by the link {{build.link}}
  when:
    status:
    - failure

trigger:
  cron: [ weekly ]


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

&lt;/div&gt;

&lt;p&gt;We think the yaml syntax speaks well by itself. In the first step, we use node:10-alpine as a base image and run npm outdated. In the second step, a Slack notification is executed only if there is something to update(npm outdated exited with error exit code). To get the Slack webhook URL, visit this &lt;a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks?next_id=0" rel="noopener noreferrer"&gt;page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the latest lines, the whole pipeline is triggered by the Cron Job labeled “outdated.” For our projects, we set that job for weekly execution, since we don’t plan to update packages everytime a new release comes.&lt;/p&gt;

&lt;p&gt;To define the task in Drone, go to Project -&amp;gt; Settings.&lt;/p&gt;

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

&lt;p&gt;Through this interface, you can choose the name of the job (which is used for pipeline filtering), the branch and the interval, which can be hourly, daily, weekly, monthly or yearly. &lt;/p&gt;

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

&lt;h3&gt;
  
  
  Automated npm audit and fix PR creation
&lt;/h3&gt;

&lt;p&gt;The npm audit command will check your app for vulnerabilities and update packages to any version current version where needed. The pipeline is very similar to the previous one, but with an extra step involving PR creation. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

kind: pipeline
name: npm audit

steps:
- name: audit
  image: node:10-alpine
  commands:
    - set -o pipefail &amp;amp;&amp;amp; npm audit --force 2&amp;gt;&amp;amp;1 | tee audit.log

- name: audit fix
  image: node:10-alpine
  commands:
    - npm audit fix
  when:
    status:
    - failure

- name: create_fix_pr
  image: lnikell/github-hub:2.11.2
  environment:
    GITHUB_TOKEN:
      from_secret: github_token
  commands:
    - git config --global user.email "email@example.com"
    - git config --global user.name "example"
    - git checkout -b drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
    - git add package.json package-lock.json
    - git commit -m 'npm audit fix'
    - git push origin drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
    - hub pull-request -m "[Security] NPM Audit Fix" -m "$(cat audit.log | tail -2)" -m "${DRONE_BUILD_LINK}"
  when:
    status:
    - failure

- name: slack_notification
  image: plugins/slack
  settings:
    webhook: https://hooks.slack.com/services/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
    template: &amp;gt;
      NPN detected vulnerable packages at *{{repo.name}}* for *{{build.branch}}* branch. 
      Report available by the link {{build.link}}
  when:
    status:
    - failure


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

&lt;/div&gt;

&lt;p&gt;In the first step, we use the same node:10-alpine image and run NPM audit. We also save an audit.log file containing the results in order to output to PR later. If vulnerable packages were found during the npm audit, the next step will fail, trigger the nmp audit fix process and pull request creation. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

-name: audit fix
 image: node:10-alpine
 commands:
   - npm audit fix
 when:
   status:
   - failure


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

&lt;/div&gt;

&lt;p&gt;In order to create a pull request, we use  hub – the command line tool for dealing with Github API. We need to generate a Github Personal Token to use it for an API call. Go to this page and create a new one: &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;https://github.com/settings/tokens&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Select “repo” permissions scope, then add your generated token to secrets in Drone with the name “github_token”. &lt;/p&gt;

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

&lt;p&gt;This is used as environment variable in the step below.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

- name: create_fix_pr
 image: lnikell/github-hub:2.11.2
 environment:
   GITHUB_TOKEN:
     from_secret: github_token
 commands:
   - git config --global user.email "lnikell@gmail.com"
   - git config --global user.name "drone"
   - git checkout -b drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
   - git add package.json package-lock.json
   - git commit -m 'npm audit fix'
   - git push origin drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
   - hub pull-request -m "[Security] NPM Audit Fix" -m "$(cat audit.log | tail -2)" -m "${DRONE_BUILD_LINK}"
 when:
   status:
   - failure


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

&lt;/div&gt;

&lt;p&gt;In this step, we declare the pattern for branch creation and create a pull request with the last two lines from the audit.log. This gives us a nice PR: &lt;/p&gt;

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

&lt;p&gt;Finally, we need to look at the trigger part of the pipeline. Since you only want to execute those checks as a part of Cron job, you need to add the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

trigger:
 cron: [ name_of_the_job ]


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

&lt;/div&gt;

&lt;p&gt;However, remember that you still need think about your main pipeline. To prevent it from running during the Cron tasks, you have to use the exclude option like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

trigger:
 cron:
   exclude: [ name_of_the_job ]


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

&lt;/div&gt;

&lt;p&gt;See an example giving you a useful overview of all pipelines &lt;strong&gt;&lt;a href="https://gist.github.com/lnikell/b7caff94900bddf1a71f9e4543ecc787" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;That was just one example of how recurring tasks on CI can be useful to you for the purposes of building, testing and fixing.  You only have to set up it once and you’ll be informed of the security of your project on a daily/weekly basis. The approach we use in our examples should be easily adaptable for Travis CI or Gitlab; if you do it this way, please share your pipeline here. &lt;/p&gt;

&lt;p&gt;If you like this article, subscribe to my &lt;a href="https://twitter.com/alex_barashkov" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://dev.to/alex_barashkov"&gt;DEV.TO&lt;/a&gt; pages.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>devops</category>
      <category>node</category>
    </item>
  </channel>
</rss>
