<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>React on Smashing Magazine — For Web Designers And Developers</title><link>https://www.smashingmagazine.com/category/react/index.xml</link><description>Recent content in React on Smashing Magazine — For Web Designers And Developers</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 09 Feb 2026 03:03:08 +0000</lastBuildDate><item><author>Teng Wei Herr</author><title>Adaptive Video Streaming With Dash.js In React</title><link>https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/</link><pubDate>Thu, 27 Mar 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/</guid><description>HTML &lt;code>&amp;lt;video&amp;gt;&lt;/code> is the de facto element we turn to for embedding video content, but it comes with constraints. For example, it downloads the video file linearly over HTTP, which leads to performance hiccups, especially for large videos consumed on slower connections. But with adaptive bitrate streaming, we can split the video into multiple segments at different bitrates and resolutions.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/" />
              <title>Adaptive Video Streaming With Dash.js In React</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Adaptive Video Streaming With Dash.js In React</h1>
                  
                    
                    <address>Teng Wei Herr</address>
                  
                  <time datetime="2025-03-27T13:00:00&#43;00:00" class="op-published">2025-03-27T13:00:00+00:00</time>
                  <time datetime="2025-03-27T13:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>I was recently tasked with creating video reels that needed to be played smoothly under a slow network or on low-end devices. I started with the native HTML5 <code>&lt;video&gt;</code> tag but quickly hit a wall &mdash; it just doesn’t cut it when connections are slow or devices are underpowered.</p>

<p>After some research, I found that <strong>adaptive bitrate streaming</strong> was the solution I needed. But here’s the frustrating part: finding a comprehensive, beginner-friendly guide was so difficult. The resources on MDN and other websites were helpful but lacked the end-to-end tutorial I was looking for.</p>

<p>That’s why I’m writing this article: to provide you with the step-by-step guide I wish I had found. I’ll bridge the gap between writing FFmpeg scripts, encoding video files, and implementing the DASH-compatible video player (<a href="https://dashjs.org/">Dash.js</a>) with code examples you can follow.</p>

<h2 id="going-beyond-the-native-html5-video-tag">Going Beyond The Native HTML5 <code>&lt;video&gt;</code> Tag</h2>

<p>You might be wondering why you can’t simply rely on the HTML <code>&lt;video&gt;</code> element. There’s a good reason for that. Let’s compare the difference between a native <code>&lt;video&gt;</code> element and adaptive video streaming in browsers.</p>

<h3 id="progressive-download">Progressive Download</h3>

<p>With progressive downloading, your browser downloads the video file linearly from the server over HTTP and starts playback as long as it has buffered enough data. This is the default behavior of the <code>&lt;video&gt;</code> element.</p>

<pre><code class="language-html">&lt;video src="rabbit320.mp4" /&gt;
</code></pre>

<p>When you play the video, check your browser’s network tab, and you’ll see multiple requests with the <code>206 Partial Content</code> status code.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="140"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png"
			
			sizes="100vw"
			alt="HTTP 206 Range Requests"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests">HTTP 206 Range Requests</a> to fetch the video file in chunks. The server sends specific byte ranges of the video to your browser. When you seek, the browser will make more range requests asking for new byte ranges (e.g., “Give me bytes 1,000,000–2,000,000”).</p>

<p class="c-pre-sidenote--left">In other words, it doesn’t fetch the entire file all at once. Instead, it delivers partial byte ranges from the single MP4 video file on demand. This is still considered a <strong>progressive download</strong> because only a single file is fetched over HTTP &mdash; there is no bandwidth or quality adaptation.</p>
<p class="c-sidenote c-sidenote--right">If the server or browser doesn’t support range requests, the entire video file will be downloaded in a single request, returning a <code>200 OK</code> status code. In that case, the video can only begin playing once the entire file has finished downloading.</p> 

<p><strong>The problems?</strong> If you’re on a slow connection trying to watch high-resolution video, you’ll be waiting a long time before playback starts.</p>

<h3 id="adaptive-bitrate-streaming">Adaptive Bitrate Streaming</h3>

<p>Instead of serving one single video file, <strong>adaptive bitrate (ABR) streaming</strong> splits the video into multiple segments at different bitrates and resolutions. During playback, the ABR algorithm will automatically select the highest quality segment that can be downloaded in time for smooth playback based on your network connectivity, bandwidth, and other device capabilities. It continues adjusting throughout to adapt to changing conditions.</p>

<p>This magic happens through two key browser technologies:</p>

<ul>
<li><strong>Media Source Extension (MSE)</strong><br />
It allows passing a <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaSource">MediaSource</a> object to the <code>src</code> attribute in <code>&lt;video&gt;</code>, enabling sending multiple <a href="https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer">SourceBuffer</a> objects that represent video segments.
<br /></li>
</ul>

<div class="break-out">
<pre><code class="language-javascript">&lt;video src="blob:https://example.com/6e31fe2a-a0a8-43f9-b415-73dc02985892" /&gt;</code></pre>
</div>
    

<ul>
<li><strong>Media Capabilities API</strong><br />
It provides information on your device’s video decoding and encoding abilities, enabling ABR to make informed decisions about which resolution to deliver.</li>
</ul>

<p>Together, they enable the core functionality of ABR, serving video chunks optimized for your specific device limitations in real time.</p>

<h2 id="streaming-protocols-mpeg-dash-vs-hls">Streaming Protocols: MPEG-DASH Vs. HLS</h2>

<p>As mentioned above, to stream media adaptively, a video is split into chunks at different quality levels across various time points. We need to facilitate the process of switching between these segments adaptively in real time. To achieve this, ABR streaming relies on specific protocols. The two most common ABR protocols are:</p>

<ul>
<li>MPEG-DASH,</li>
<li>HTTP Live Streaming (HLS).</li>
</ul>

<p>Both of these protocols utilize HTTP to send video files. Hence, they are compatible with HTTP web servers.</p>

<p>This article focuses on MPEG-DASH. However, it’s worth noting that DASH isn’t supported by Apple devices or browsers, as mentioned in <a href="https://www.mux.com/articles/hls-vs-dash-what-s-the-difference-between-the-video-streaming-protocols">Mux’s article</a>.</p>

<h3 id="mpeg-dash">MPEG-DASH</h3>

<p>MPEG-DASH enables adaptive streaming through:</p>

<ul>
<li><strong>A Media Presentation Description (MPD) file</strong><br />
This XML manifest file contains information on how to select and manage streams based on adaptive rules.</li>
<li><strong>Segmented Media Files</strong><br />
Video and audio files are divided into segments at different resolutions and durations using MPEG-DASH-compliant codecs and formats.</li>
</ul>

<p>On the client side, a DASH-compliant video player reads the MPD file and continuously monitors network bandwidth. Based on available bandwidth, the player selects the appropriate bitrate and requests the corresponding video chunk. This process repeats throughout playback, ensuring smooth, optimal quality.</p>

<p>Now that you understand the fundamentals, let’s build our adaptive video player!</p>

<h2 id="steps-to-build-an-adaptive-bitrate-streaming-video-player">Steps To Build an Adaptive Bitrate Streaming Video Player</h2>

<p>Here’s the plan:</p>

<ol>
<li>Transcode the MP4 video into audio and video renditions at different resolutions and bitrates with FFmpeg.</li>
<li>Generate an MPD file with FFmpeg.</li>
<li>Serve the output files from the server.</li>
<li>Build the DASH-compatible video player to play the video.</li>
</ol>

<h3 id="install-ffmpeg">Install FFmpeg</h3>

<p>For macOS users, install FFmpeg using Brew by running the following command in your terminal:</p>

<pre><code class="language-bash">brew install ffmpeg
</code></pre>

<p>For other operating systems, please <a href="https://www.ffmpeg.org/download.html">refer to FFmpeg’s documentation</a>.</p>

<h3 id="generate-audio-rendition">Generate Audio Rendition</h3>

<p>Next, run the following script to extract the audio track and encode it in WebM format for DASH compatibility:</p>

<div class="break-out">
<pre><code class="language-bash">ffmpeg -i "input_video.mp4" -vn -acodec libvorbis -ab 128k "audio.webm"
</code></pre>
</div>

<ul>
<li><code>-i &quot;input_video.mp4&quot;</code>: Specifies the input video file.</li>
<li><code>-vn</code>: Disables the video stream (audio-only output).</li>
<li><code>-acodec libvorbis</code>: Uses the <a href="https://ffmpeg.org/ffmpeg-codecs.html#libvorbis"><strong>libvorbis</strong></a> codec to encode audio.</li>
<li><code>-ab 128k</code>: Sets the audio bitrate to <strong>128 kbps</strong>.</li>
<li><code>&quot;audio.webm&quot;</code>: Specifies the output audio file in WebM format.</li>
</ul>

<h3 id="generate-video-renditions">Generate Video Renditions</h3>

<p>Run this script to create three video renditions with varying resolutions and bitrates. The largest resolution should match the input file size. For example, if the input video is <strong>576×1024</strong> at 30 frames per second (fps), the script generates renditions optimized for vertical video playback.</p>

<div class="break-out">
<pre><code class="language-bash">ffmpeg -i "input_video.mp4" -c:v libvpx-vp9 -keyint_min 150 -g 150 \
-tile-columns 4 -frame-parallel 1 -f webm \
-an -vf scale=576:1024 -b:v 1500k "input_video_576x1024_1500k.webm" \
-an -vf scale=480:854 -b:v 1000k "input_video_480x854_1000k.webm" \
-an -vf scale=360:640 -b:v 750k "input_video_360x640_750k.webm"
</code></pre>
</div>

<ul>
<li><code>-c:v libvpx-vp9</code>: Uses the <a href="https://trac.ffmpeg.org/wiki/Encode/VP9"><strong>libvpx-vp9</strong></a> as the VP9 video encoder for WebM.</li>
<li><code>-keyint_min 150</code> and <code>-g 150</code>: Set a <strong>150-frame keyframe interval</strong> (approximately every 5 seconds at 30 fps). This allows bitrate switching every 5 seconds.</li>
<li><code>-tile-columns 4</code> and <code>-frame-parallel 1</code>: Optimize encoding performance through parallel processing.</li>
<li><code>-f webm</code>: Specifies the output format as WebM.</li>
</ul>

<p>In each rendition:</p>

<ul>
<li><code>-an</code>: Excludes audio (video-only output).</li>
<li><code>-vf scale=576:1024</code>: Scales the video to a resolution of <strong>576x1024</strong> pixels.</li>
<li><code>-b:v 1500k</code>: Sets the video bitrate to <strong>1500 kbps</strong>.</li>
</ul>

<p>WebM is chosen as the output format, as they are smaller in size and optimized yet widely compatible with most web browsers.</p>

<h3 id="generate-mpd-manifest-file">Generate MPD Manifest File</h3>

<p>Combine the video renditions and audio track into a DASH-compliant MPD manifest file by running the following script:</p>

<div class="break-out">
<pre><code class="language-bash">ffmpeg \
  -f webm_dash_manifest -i "input_video_576x1024_1500k.webm" \
  -f webm_dash_manifest -i "input_video_480x854_1000k.webm" \
  -f webm_dash_manifest -i "input_video_360x640_750k.webm" \
  -f webm_dash_manifest -i "audio.webm" \
  -c copy \
  -map 0 -map 1 -map 2 -map 3 \
  -f webm_dash_manifest \
  -adaptation_sets "id=0,streams=0,1,2 id=1,streams=3" \
  "input_video_manifest.mpd"
</code></pre>
</div>

<ul>
<li><code>-f webm_dash_manifest -i &quot;…&quot;</code>: Specifies the inputs so that the ASH video player will switch between them dynamically based on network conditions.</li>
<li><code>-map 0 -map 1 -map 2 -map 3</code>: Includes all video (0, 1, 2) and audio (3) in the final manifest.</li>
<li><code>-adaptation_sets</code>: Groups streams into adaptation sets:

<ul>
<li><code>id=0,streams=0,1,2</code>: Groups the video renditions into a single adaptation set.</li>
<li><code>id=1,streams=3</code>: Assigns the audio track to a separate adaptation set.</li>
</ul></li>
</ul>

<p>The resulting MPD file (<code>input_video_manifest.mpd</code>) describes the streams and enables adaptive bitrate streaming in MPEG-DASH.</p>

<div class="break-out">
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;MPD
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="urn:mpeg:DASH:schema:MPD:2011"
  xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011"
  type="static"
  mediaPresentationDuration="PT81.166S"
  minBufferTime="PT1S"
  profiles="urn:mpeg:dash:profile:webm-on-demand:2012"&gt;

  &lt;Period id="0" start="PT0S" duration="PT81.166S"&gt;
    &lt;AdaptationSet
      id="0"
      mimeType="video/webm"
      codecs="vp9"
      lang="eng"
      bitstreamSwitching="true"
      subsegmentAlignment="false"
      subsegmentStartsWithSAP="1"&gt;
      
      &lt;Representation id="0" bandwidth="1647920" width="576" height="1024"&gt;
        &lt;BaseURL&gt;input_video_576x1024_1500k.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="16931581-16931910"&gt;
          &lt;Initialization range="0-645" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
      &lt;Representation id="1" bandwidth="1126977" width="480" height="854"&gt;
        &lt;BaseURL&gt;input_video_480x854_1000k.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="11583599-11583986"&gt;
          &lt;Initialization range="0-645" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
      &lt;Representation id="2" bandwidth="843267" width="360" height="640"&gt;
        &lt;BaseURL&gt;input_video_360x640_750k.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="8668326-8668713"&gt;
          &lt;Initialization range="0-645" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
    &lt;/AdaptationSet&gt;
    
    &lt;AdaptationSet
      id="1"
      mimeType="audio/webm"
      codecs="vorbis"
      lang="eng"
      audioSamplingRate="44100"
      bitstreamSwitching="true"
      subsegmentAlignment="true"
      subsegmentStartsWithSAP="1"&gt;
      
      &lt;Representation id="3" bandwidth="89219"&gt;
        &lt;BaseURL&gt;audio.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="921727-922055"&gt;
          &lt;Initialization range="0-4889" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
    &lt;/AdaptationSet&gt;
  &lt;/Period&gt;
&lt;/MPD&gt;
</code></pre>
</div>

<p>After completing these steps, you’ll have:</p>

<ol>
<li>Three video renditions (<code>576x1024</code>, <code>480x854</code>, <code>360x640</code>),</li>
<li>One audio track, and</li>
<li>An MPD manifest file.</li>
</ol>

<pre><code class="language-bash">input&#95;video.mp4
audio.webm
input&#95;video&#95;576x1024&#95;1500k.webm
input&#95;video&#95;480x854&#95;1000k.webm
input&#95;video&#95;360x640&#95;750k.webm
input&#95;video&#95;manifest.mpd
</code></pre>

<p>The original video <code>input_video.mp4</code> should also be kept to serve as a fallback video source later.</p>

<h3 id="serve-the-output-files">Serve The Output Files</h3>

<p>These output files can now be uploaded to cloud storage (e.g., AWS S3 or Cloudflare R2) for playback. While they can be served directly from a local folder, I highly recommend storing them in cloud storage and leveraging a CDN to cache the assets for better performance. Both AWS and Cloudflare support HTTP range requests out of the box.</p>

<h3 id="building-the-dash-compatible-video-player-in-react">Building The DASH-Compatible Video Player In React</h3>

<p>There’s nothing like a real-world example to help understand how everything works. There are different ways we can implement a DASH-compatible video player, but I’ll focus on an approach using React.</p>

<p>First, install the <a href="https://github.com/Dash-Industry-Forum/dash.js">Dash.js</a> npm package by running:</p>

<pre><code class="language-bash">npm i dashjs
</code></pre>

<p>Next, create a component called <code>&lt;DashVideoPlayer /&gt;</code> and initialize the Dash <a href="https://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html">MediaPlayer</a> instance by pointing it to the MPD file when the component mounts.</p>

<p class="c-pre-sidenote--left">The ref callback function runs upon the component mounting, and within the callback function, <code>playerRef</code> will refer to the actual Dash <a href="https://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html">MediaPlayer</a> instance and be bound with event listeners. We also include the original MP4 URL in the <code>&lt;source&gt;</code> element as a fallback if the browser doesn’t support MPEG-DASH.</p>
<p class="c-sidenote c-sidenote--right">If you’re using <strong>Next.js app router</strong>, remember to add the <code>‘use client’</code> directive to enable client-side hydration, as the video player is only initialized on the client side.</p>

<p>Here is the full example:</p>

<div class="break-out">
<pre><code class="language-javascript">import dashjs from 'dashjs'
import { useCallback, useRef } from 'react'

export const DashVideoPlayer = () =&gt; {
  const playerRef = useRef()

  const callbackRef = useCallback((node) =&gt; {
    if (node !== null) {  
      playerRef.current = dashjs.MediaPlayer().create()

      playerRef.current.initialize(node, "https://example.com/uri/to/input&#95;video&#95;manifest.mpd", false)
  
      playerRef.current.on('canPlay', () =&gt; {
        // upon video is playable
      })
  
      playerRef.current.on('error', (e) =&gt; {
        // handle error
      })
  
      playerRef.current.on('playbackStarted', () =&gt; {
        // handle playback started
      })
  
      playerRef.current.on('playbackPaused', () =&gt; {
        // handle playback paused
      })
  
      playerRef.current.on('playbackWaiting', () =&gt; {
        // handle playback buffering
      })
    }
  },[])

  return (
    &lt;video ref={callbackRef} width={310} height={548} controls&gt;
      &lt;source src="https://example.com/uri/to/input&#95;video.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
  )
}</code></pre>
</div>

<h3 id="result">Result</h3>

<p>Observe the changes in the video file when the network connectivity is adjusted from Fast 4G to 3G using Chrome DevTools. It switches from 480p to 360p, showing how the experience is optimized for more or less available bandwidth.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1070045535"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>ABR example</figcaption>
	
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>That’s it! We just implemented a working DASH-compatible video player in React to establish a video with adaptive bitrate streaming. Again, the benefits of this are rooted in <strong>performance</strong>. When we adopt ABR streaming, we’re requesting the video in smaller chunks, allowing for more immediate playback than we’d get if we needed to fully download the video file first. And we’ve done it in a way that supports multiple versions of the same video, allowing us to serve the best format for the user’s device.</p>

<h3 id="references">References</h3>

<ul>
<li>“<a href="https://www.zeng.dev/post/2023-http-range-and-play-mp4-in-browser/">Http Range Request And MP4 Video Play In Browser</a>,” Zeng Xu</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Media/Audio_and_video_delivery/Setting_up_adaptive_streaming_media_sources">Setting up adaptive streaming media sources</a> (Mozilla Developer Network)</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Media/DASH_Adaptive_Streaming_for_HTML_5_Video">DASH Adaptive Streaming for HTML video</a> (Mozilla Developer Network)</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Lazar Nikolov</author><title>The Forensics Of React Server Components (RSCs)</title><link>https://www.smashingmagazine.com/2024/05/forensics-react-server-components/</link><pubDate>Thu, 09 May 2024 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/05/forensics-react-server-components/</guid><description>We love client-side rendering for the way it relieves the server of taxing operations, but serving an empty HTML page often leads to taxing user experiences during the initial page load. We love server-side rendering because it allows us to serve static assets on speedy CDNs, but they’re unfit for large-scale projects with dynamic content. React Server Components (RSCs) combine the best of both worlds, and author Lazar Nikolov thoroughly examines how we got here with a deep look at the impact that RSCs have on the page load timeline.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/05/forensics-react-server-components/" />
              <title>The Forensics Of React Server Components (RSCs)</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The Forensics Of React Server Components (RSCs)</h1>
                  
                    
                    <address>Lazar Nikolov</address>
                  
                  <time datetime="2024-05-09T13:00:00&#43;00:00" class="op-published">2024-05-09T13:00:00+00:00</time>
                  <time datetime="2024-05-09T13:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Sentry.io</b></p>
                

<p>In this article, we’re going to look deeply at React Server Components (RSCs). They are the latest innovation in React’s ecosystem, leveraging both server-side and client-side rendering as well as <a href="https://en.wikipedia.org/wiki/Chunked_transfer_encoding">streaming HTML</a> to deliver content as fast as possible.</p>

<p>We will get really nerdy to get a full understanding of how RSCs fit into the React picture, the level of control they offer over the rendering lifecycle of components, and what page loads look like with RSCs in place.</p>

<p>But before we dive into all of that, I think it’s worth looking back at how React has rendered websites up until this point to set the context for why we need RSCs in the first place.</p>

<h2 id="the-early-days-react-client-side-rendering">The Early Days: React Client-Side Rendering</h2>

<p>The first React apps were rendered on the client side, i.e., in the browser. As developers, we wrote apps with JavaScript classes as components and packaged everything up using bundlers, like Webpack, in a nicely compiled and tree-shaken heap of code ready to ship in a production environment.</p>

<p>The HTML that returned from the server contained a few things, including:</p>

<ul>
<li>An HTML document with metadata in the <code>&lt;head&gt;</code> and a blank <code>&lt;div&gt;</code> in the <code>&lt;body&gt;</code> used as a hook to inject the app into the DOM;</li>
<li>JavaScript resources containing React’s core code and the actual code for the web app, which would generate the user interface and populate the app inside of the empty <code>&lt;div&gt;</code>.</li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="566"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg"
			
			sizes="100vw"
			alt="Diagram of the client-side rendering process of a React app, starting with a blank loading page in the browser followed by a series of processes connected to CDNs and APIs to produce content on the loading page."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 1. (<a href='https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>A web app under this process is only fully interactive once JavaScript has fully completed its operations. You can probably already see the tension here that comes with an <strong>improved developer experience (DX) that negatively impacts the user experience (UX)</strong>.</p>

<p>The truth is that there were (and are) pros and cons to CSR in React. Looking at the positives, web applications delivered <strong>smooth, quick transitions</strong> that reduced the overall time it took to load a page, thanks to reactive components that update with user interactions without triggering page refreshes. CSR lightens the server load and allows us to serve assets from speedy content delivery networks (CDNs) capable of delivering content to users from a server location geographically closer to the user for even more optimized page loads.</p>

<p>There are also not-so-great consequences that come with CSR, most notably perhaps that components could fetch data independently, leading to <a href="https://blog.sentry.io/fetch-waterfall-in-react/"><strong>waterfall network requests</strong></a> that dramatically slow things down. This may sound like a minor nuisance on the UX side of things, but the damage can actually be quite large on a human level. Eric Bailey’s “<a href="https://ericwbailey.design/published/modern-health-frameworks-performance-and-harm/">Modern Health, frameworks, performance, and harm</a>” should be a cautionary tale for all CSR work.</p>

<p>Other negative CSR consequences are not quite as severe but still lead to damage. For example, it used to be that an HTML document containing nothing but metadata and an empty <code>&lt;div&gt;</code> was illegible to search engine crawlers that never get the fully-rendered experience. While that’s solved today, the SEO hit at the time was an anchor on company sites that rely on search engine traffic to generate revenue.</p>

<h2 id="the-shift-server-side-rendering-ssr">The Shift: Server-Side Rendering (SSR)</h2>

<p>Something needed to change. CSR presented developers with a powerful new approach for constructing speedy, interactive interfaces, but users everywhere were inundated with blank screens and loading indicators to get there. The solution was to move the rendering experience from the <strong>client</strong> to the <strong>server</strong>. I know it sounds funny that we needed to improve something by going back to the way it was before.</p>

<p>So, yes, React gained server-side rendering (SSR) capabilities. At one point, SSR was such a topic in the React community that <a href="https://sentry.io/resources/moving-to-server-side-rendering/">it had a moment</a> in the spotlight. The move to SSR brought significant changes to app development, specifically in how it influenced React behavior and how content could be delivered by way of servers instead of browsers.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="600"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg"
			
			sizes="100vw"
			alt="Diagram of the server-side rendering process of a React app, starting with a blank loading page in the browser followed by a screen of un-interactive content, then a fully interactive page of content."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 2. (<a href='https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="addressing-csr-limitations">Addressing CSR Limitations</h3>

<p>Instead of sending a blank HTML document with SSR, we rendered the initial HTML on the server and sent it to the browser. The browser was able to immediately start displaying the content without needing to show a loading indicator. This significantly improves the <a href="https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#first-contentful-paint-fcp">First Contentful Paint (FCP) performance metric in Web Vitals</a>.</p>

<p>Server-side rendering also fixed the SEO issues that came with CSR. Since the crawlers received the content of our websites directly, they were then able to index it right away. The data fetching that happens initially also takes place on the server, which is a plus because it’s closer to the data source and can eliminate fetch waterfalls <a href="https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall"><em>if done properly</em></a>.</p>

<h3 id="hydration">Hydration</h3>

<p>SSR has its own complexities. For React to make the static HTML received from the server interactive, it needs to <strong>hydrate</strong> it. Hydration is the process that happens when React reconstructs its Virtual Document Object Model (DOM) on the client side based on what was in the DOM of the initial HTML.</p>

<blockquote><strong>Note</strong>: React maintains its own <a href="https://legacy.reactjs.org/docs/faq-internals.html">Virtual DOM</a> because it’s faster to figure out updates on it instead of the actual DOM. It synchronizes the actual DOM with the Virtual DOM when it needs to update the UI but performs the diffing algorithm on the Virtual DOM.</blockquote>

<p>We now have two flavors of Reacts:</p>

<ol>
<li><strong>A server-side flavor</strong> that knows how to render static HTML from our component tree,</li>
<li><strong>A client-side flavor</strong> that knows how to make the page interactive.</li>
</ol>

<p>We’re still shipping React and code for the app to the browser because &mdash; in order to hydrate the initial HTML &mdash; React needs the same components on the client side that were used on the server. During hydration, <a href="https://css-tricks.com/how-react-reconciliation-works/">React performs a process called</a> <a href="https://css-tricks.com/how-react-reconciliation-works/"><em>reconciliation</em></a> in which it compares the server-rendered DOM with the client-rendered DOM and tries to identify differences between the two. If there are differences between the two DOMs, React attempts to fix them by rehydrating the component tree and updating the component hierarchy to match the server-rendered structure. And if there are <em>still</em> inconsistencies that cannot be resolved, React will throw errors to indicate the problem. This problem is commonly known as a <em>hydration error</em>.</p>

<h3 id="ssr-drawbacks">SSR Drawbacks</h3>

<p>SSR is not a silver bullet solution that addresses CSR limitations. SSR comes with its own drawbacks. Since we moved the initial HTML rendering and data fetching to the server, those servers are now experiencing a much greater load than when we loaded everything on the client.</p>

<p>Remember when I mentioned that SSR generally improves the FCP performance metric? That may be true, but the <a href="https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#time-to-first-byte-ttfb">Time to First Byte (TTFB) performance metric</a> took a negative hit with SSR. The browser literally has to wait for the server to fetch the data it needs, generate the initial HTML, and send the first byte. And while TTFB is not a Core Web Vital metric in itself, it influences the metrics. A negative TTFB leads to negative Core Web Vitals metrics.</p>

<p>Another drawback of SSR is that the entire page is unresponsive until client-side React has finished hydrating it. Interactive elements cannot listen and “react” to user interactions before React hydrates them, i.e., React attaches the intended event listeners to them. The hydration process is typically fast, but the internet connection and hardware capabilities of the device in use can slow down rendering by a noticeable amount.</p>

<h2 id="the-present-a-hybrid-approach">The Present: A Hybrid Approach</h2>

<p>So far, we have covered two different flavors of React rendering: CSR and SSR. While the two were attempts to improve one another, we now get the best of both worlds, so to speak, as SSR has branched into three additional React flavors that offer a hybrid approach in hopes of reducing the limitations that come with CSR and SSR.</p>

<p>We’ll look at the first two &mdash; <strong>static site generation</strong> and <strong>incremental static regeneration</strong> &mdash; before jumping into an entire discussion on React Server Components, the third flavor.</p>

<h3 id="static-site-generation-ssg">Static Site Generation (SSG)</h3>

<p>Instead of regenerating the same HTML code on every request, we came up with SSG. This React flavor compiles and builds the entire app at build time, generating static (as in vanilla HTML and CSS) files that are, in turn, hosted on a speedy CDN.</p>

<p>As you might suspect, this hybrid approach to rendering is a nice fit for smaller projects where the content doesn’t change much, like a marketing site or a personal blog, as opposed to larger projects where content may change with user interactions, like an e-commerce site.</p>

<p>SSG reduces the burden on the server while improving performance metrics related to TTFB because the server no longer has to perform heavy, expensive tasks for re-rendering the page.</p>

<h3 id="incremental-static-regeneration-isr">Incremental Static Regeneration (ISR)</h3>

<p>One SSG drawback is having to rebuild all of the app’s code when a content change is needed. The content is set in stone &mdash; being static and all &mdash; and there’s no way to change just one part of it without rebuilding the whole thing.</p>

<p>The Next.js team created the second hybrid flavor of React that addresses the drawback of complete SSG rebuilds: <strong>incremental static regeneration (ISR)</strong>. The name says a lot about the approach in that ISR only rebuilds what’s needed instead of the entire thing. We generate the “initial version” of the page statically during build time but are also able to rebuild any page containing stale data <em>after</em> a user lands on it (i.e., the server request triggers the data check).</p>

<p>From that point on, the server will serve new versions of that page statically in increments when needed. That makes ISR a hybrid approach that is neatly positioned between SSG and traditional SSR.</p>

<p>At the same time, ISR does not address the “stale content” symptom, where users may visit a page before it has finished being generated. Unlike SSG, ISR needs an actual server to regenerate individual pages in response to a user’s browser making a server request. That means we lose the valuable ability to deploy ISR-based apps on a CDN for optimized asset delivery.</p>

<h2 id="the-future-react-server-components">The Future: React Server Components</h2>

<p>Up until this point, we’ve juggled between CSR, SSR, SSG, and ISR approaches, where all make some sort of trade-off, negatively affecting performance, development complexity, and user experience. Newly introduced <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">React Server Components</a> (RSC) aim to address most of these drawbacks by allowing us &mdash; the developer &mdash; to <strong>choose the right rendering strategy for each individual React component</strong>.</p>

<p>RSCs can significantly reduce the amount of JavaScript shipped to the client since we can selectively decide which ones to serve statically on the server and which render on the client side. There’s a lot more control and flexibility for striking the right balance for your particular project.</p>

<blockquote><strong>Note:</strong> It’s important to keep in mind that as we adopt more advanced architectures, like RSCs, monitoring solutions become invaluable. Sentry offers robust <a href="https://docs.sentry.io/product/performance/">performance monitoring</a> and error-tracking capabilities that help you keep an eye on the real-world performance of your RSC-powered application. Sentry also helps you gain insights into how your releases are performing and how stable they are, which is yet another crucial feature to have while migrating your existing applications to RSCs. Implementing Sentry in an RSC-enabled framework like <a href="https://sentry.io/for/nextjs/">Next.js</a> is as easy as running a single terminal command.</blockquote>

<p>But what exactly <em>is</em> an RSC? Let’s pick one apart to see how it works under the hood.</p>

<h2 id="the-anatomy-of-react-server-components">The Anatomy of React Server Components</h2>

<p>This new approach introduces two types of rendering components: <strong>Server Components</strong> and <strong>Client Components</strong>. The differences between these two are not <em>how</em> they function but <em>where</em> they execute and the environments they’re designed for. At the time of this writing, the only way to use RSCs is through React frameworks. And at the moment, there are only three frameworks that support them: <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">Next.js</a>, <a href="https://www.gatsbyjs.com/docs/conceptual/partial-hydration/">Gatsby</a>, and <a href="https://redwoodjs.com/blog/rsc-now-in-redwoodjs">RedwoodJS</a>.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="763"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg"
			
			sizes="100vw"
			alt="Wire diagram showing connected server components and client components represented as gray and blue dots, respectively."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 3: Example of an architecture consisting of Server Components and Client Components. (<a href='https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="server-components">Server Components</h3>

<p>Server Components are designed to be executed on the server, and their code is never shipped to the browser. The HTML output and any props they might be accepting are the only pieces that are served. This approach has multiple performance benefits and user experience enhancements:</p>

<ul>
<li><strong>Server Components allow for large dependencies to remain on the server side.</strong><br />
Imagine using a large library for a component. If you’re executing the component on the client side, it means that you’re also shipping the full library to the browser. With Server Components, you’re only taking the static HTML output and avoiding having to ship any JavaScript to the browser. Server Components are truly static, and they remove the whole hydration step.</li>
<li><strong>Server Components are located much closer to the data sources &mdash; e.g., databases or file systems &mdash; they need to generate code.</strong><br />
They also leverage the server’s computational power to speed up compute-intensive rendering tasks and send only the generated results back to the client. They are also generated in a single pass, which <a href="https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall">avoids request waterfalls and HTTP round trips</a>.</li>
<li><strong>Server Components safely keep sensitive data and logic away from the browser.</strong><br />
That’s thanks to the fact that personal tokens and API keys are executed on a secure server rather than the client.</li>
<li><strong>The rendering results can be cached and reused between subsequent requests and even across different sessions.</strong><br />
This significantly reduces rendering time, as well as the overall amount of data that is fetched for each request.</li>
</ul>

<p>This architecture also makes use of <strong>HTML streaming</strong>, which means the server defers generating HTML for specific components and instead renders a fallback element in their place while it works on sending back the generated HTML. Streaming Server Components wrap components in <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a> tags that provide a fallback value. The implementing framework uses the fallback initially but streams the newly generated content when it‘s ready. We’ll talk more about streaming, but let’s first look at Client Components and compare them to Server Components.</p>

<h3 id="client-components">Client Components</h3>

<p>Client Components are the components we already know and love. They’re executed on the client side. Because of this, Client Components are capable of handling user interactions and have access to the browser APIs like <code>localStorage</code> and geolocation.</p>

<p>The term “Client Component” doesn’t describe anything new; they merely are given the label to help distinguish the “old” CSR components from Server Components. Client Components are defined by a <a href="https://react.dev/reference/react/use-server"><code>&quot;use client&quot;</code></a> directive at the top of their files.</p>

<pre><code class="language-javascript">"use client"
export default function LikeButton() {
  const likePost = () =&gt; {
    // ...
  }
  return (
    &lt;button onClick={likePost}&gt;Like&lt;/button&gt;
  )
}
</code></pre>

<p>In Next.js, all components are Server Components by default. That’s why we need to explicitly define our Client Components with <code>&quot;use client&quot;</code>. There’s also a <code>&quot;use server&quot;</code> directive, but it’s used for Server Actions (which are RPC-like actions that invoked from the client, but executed on the server). You don’t use it to define your Server Components.</p>

<p>You might (rightfully) assume that Client Components are only rendered on the client, but Next.js renders Client Components on the server to generate the initial HTML. As a result, browsers can immediately start rendering them and then perform hydration later.</p>

<h3 id="the-relationship-between-server-components-and-client-components">The Relationship Between Server Components and Client Components</h3>

<p>Client Components can only <em>explicitly</em> import other Client Components. In other words, we’re unable to import a Server Component into a Client Component because of re-rendering issues. But we can have Server Components in a Client Component’s subtree &mdash; only passed through the <code>children</code> prop. Since Client Components live in the browser and they handle user interactions or define their own state, they get to re-render often. When a Client Component re-renders, so will its subtree. But if its subtree contains Server Components, how would they re-render? They don’t live on the client side. That’s why the React team put that limitation in place.</p>

<p>But hold on! We actually <em>can</em> import Server Components into Client Components. It’s just not a direct one-to-one relationship because the Server Component will be converted into a Client Component. If you’re using server APIs that you can’t use in the browser, you’ll get an error; if not &mdash; you’ll have a Server Component whose code gets “leaked” to the browser.</p>

<p>This is an incredibly important nuance to keep in mind as you work with RSCs.</p>

<h2 id="the-rendering-lifecycle">The Rendering Lifecycle</h2>

<p>Here’s the order of operations that Next.js takes to stream contents:</p>

<ol>
<li>The app router matches the page’s URL to a Server Component, builds the component tree, and instructs the server-side React to render that Server Component and all of its children components.</li>
<li>During render, React generates an “RSC Payload”. The RSC Payload informs Next.js about the page and what to expect in return, as well as what to fall back to during a <code>&lt;Suspense&gt;</code>.</li>
<li>If React encounters a suspended component, it pauses rendering that subtree and uses the suspended component’s fallback value.</li>
<li>When React loops through the last static component, Next.js prepares the generated HTML and the RSC Payload before streaming it back to the client through one or multiple chunks.</li>
<li>The client-side React then uses the instructions it has for the RSC Payload and client-side components to render the UI. It also hydrates each Client Component as they load.</li>
<li>The server streams in the suspended Server Components as they become available as an RSC Payload. Children of Client Components are also hydrated at this time if the suspended component contains any.</li>
</ol>

<p>We will look at the RSC rendering lifecycle from the browser’s perspective momentarily. For now, the following figure illustrates the outlined steps we covered.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="489"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg"
			
			sizes="100vw"
			alt="Wire diagram of the RSC rendering lifecycle going from a blank page to a page shell to a complete page."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 4: Diagram of the RSC Rendering Lifecycle. (<a href='https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We’ll see this operation flow from the browser’s perspective in just a bit.</p>

<h2 id="rsc-payload">RSC Payload</h2>

<p>The RSC payload is a special data format that the server generates as it renders the component tree, and it includes the following:</p>

<ul>
<li>The rendered HTML,</li>
<li>Placeholders where the Client Components should be rendered,</li>
<li>References to the Client Components’ JavaScript files,</li>
<li>Instructions on which JavaScript files it should invoke,</li>
<li>Any props passed from a Server Component to a Client Component.</li>
</ul>

<p>There’s no reason to worry much about the RSC payload, but it’s worth understanding what exactly the RSC payload contains. Let’s examine an example (truncated for brevity) from a <a href="https://github.com/nikolovlazar/rsc-forensics">demo app I created</a>:</p>

<div class="break-out">
<pre><code class="language-javascript">1:HL["/&#95;next/static/media/c9a5bc6a7c948fb0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
2:HL["/&#95;next/static/css/app/layout.css?v=1711137019097","style"]
0:"$L3"
4:HL["/&#95;next/static/css/app/page.css?v=1711137019097","style"]
5:I["(app-pages-browser)/./node&#95;modules/next/dist/client/components/app-router.js",["app-pages-internals","static/chunks/app-pages-internals.js"],""]
8:"$Sreact.suspense"
a:I["(app-pages-browser)/./node&#95;modules/next/dist/client/components/layout-router.js",["app-pages-internals","static/chunks/app-pages-internals.js"],""]
b:I["(app-pages-browser)/./node&#95;modules/next/dist/client/components/render-from-template-context.js",["app-pages-internals","static/chunks/app-pages-internals.js"],""]
d:I["(app-pages-browser)/./src/app/global-error.jsx",["app/global-error","static/chunks/app/global-error.js"],""]
f:I["(app-pages-browser)/./src/components/clearCart.js",["app/page","static/chunks/app/page.js"],"ClearCart"]
7:["$","main",null,{"className":"page&#95;main&#95;&#95;GlU4n","children":[["$","$Lf",null,{}],["$","$8",null,{"fallback":["$","p",null,{"children":"🌀 loading products..."}],"children":"$L10"}]]}]
c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}]...
9:["$","p",null,{"children":["🛍️ ",3]}]
11:I["(app-pages-browser)/./src/components/addToCart.js",["app/page","static/chunks/app/page.js"],"AddToCart"]
10:["$","ul",null,{"children":[["$","li","1",{"children":["Gloves"," - $",20,["$...
</code></pre>
</div>

<p>To find this code in the demo app, open your browser’s developer tools at the Elements tab and look at the <code>&lt;script&gt;</code> tags at the bottom of the page. They’ll contain lines like:</p>

<pre><code class="language-javascript">self.&#95;&#95;next&#95;f.push([1,"PAYLOAD&#95;STRING&#95;HERE"]).
</code></pre>

<p>Every line from the snippet above is an individual RSC payload. You can see that each line starts with a number or a letter, followed by a colon, and then an array that’s sometimes prefixed with letters. We won’t get into too deep in detail as to what they mean, but in general:</p>

<ul>
<li><strong><code>HL</code> payloads</strong> are called “hints” and link to specific resources like CSS and fonts.</li>
<li><strong><code>I</code> payloads</strong> are called “modules,” and they invoke specific scripts. This is how Client Components are being loaded as well. If the Client Component is part of the main bundle, it’ll execute. If it’s not (meaning it’s lazy-loaded), a fetcher script is added to the main bundle that fetches the component’s CSS and JavaScript files when it needs to be rendered. There’s going to be an <code>I</code> payload sent from the server that invokes the fetcher script when needed.</li>
<li><strong><code>&quot;$&quot;</code> payloads</strong> are DOM definitions generated for a certain Server Component. They are usually accompanied by actual static HTML streamed from the server. That’s what happens when a suspended component becomes ready to be rendered: the server generates its static HTML and RSC Payload and then streams both to the browser.</li>
</ul>

<h2 id="streaming">Streaming</h2>

<p>Streaming allows us to progressively render the UI from the server. With RSCs, each component is capable of fetching its own data. Some components are fully static and ready to be sent immediately to the client, while others require more work before loading. Based on this, Next.js splits that work into multiple chunks and streams them to the browser as they become ready. So, when a user visits a page, the server invokes all Server Components, generates the initial HTML for the page (i.e., the page shell), replaces the “suspended” components’ contents with their fallbacks, and streams all of that through one or multiple chunks back to the client.</p>

<p>The server returns a <code>Transfer-Encoding: chunked</code> header that lets the browser know to expect streaming HTML. This prepares the browser for receiving multiple chunks of the document, rendering them as it receives them. We can actually see the header when opening Developer Tools at the Network tab. Trigger a refresh and click on the document request.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="238"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg"
			
			sizes="100vw"
			alt="Response header output highlighting the line containing the chunked transfer endcoding"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 5: Providing a hint to the browser to expect HTML streaming. (<a href='https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We can also debug the way Next.js sends the chunks in a terminal with the <code>curl</code> command:</p>

<pre><code class="language-bash">curl -D - --raw localhost:3000 &gt; chunked-response.txt
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="416"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg"
			
			sizes="100vw"
			alt="Headers and chunked HTML payloads."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 6. (<a href='https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You probably see the pattern. For each chunk, the server responds with the chunk’s size before sending the chunk’s contents. Looking at the output, we can see that the server streamed the entire page in 16 different chunks. At the end, the server sends back a zero-sized chunk, indicating the end of the stream.</p>

<p>The first chunk starts with the <code>&lt;!DOCTYPE html&gt;</code> declaration. The second-to-last chunk, meanwhile, contains the closing <code>&lt;/body&gt;</code> and <code>&lt;/html&gt;</code> tags. So, we can see that the server streams the entire document from top to bottom, then pauses to wait for the suspended components, and finally, at the end, closes the body and HTML before it stops streaming.</p>

<p>Even though the server hasn’t completely finished streaming the document, the browser’s fault tolerance features allow it to draw and invoke whatever it has at the moment without waiting for the closing <code>&lt;/body&gt;</code> and <code>&lt;/html&gt;</code> tags.</p>

<h3 id="suspending-components">Suspending Components</h3>

<p>We learned from the render lifecycle that when a page is visited, Next.js matches the RSC component for that page and asks React to render its subtree in HTML. When React stumbles upon a suspended component (i.e., async function component), it grabs its fallback value from the <code>&lt;Suspense&gt;</code> component (or the <code>loading.js</code> file if it’s a Next.js route), renders that instead, then continues loading the other components. Meanwhile, the RSC invokes the async component in the background, which is streamed later as it finishes loading.</p>

<p>At this point, Next.js has returned a full page of static HTML that includes either the components themselves (rendered in static HTML) or their fallback values (if they’re suspended). It takes the static HTML and RSC payload and streams them back to the browser through one or multiple chunks.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg"
			
			sizes="100vw"
			alt="Showing suspended component fallbacks"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 7. (<a href='https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>As the suspended components finish loading, React generates HTML recursively while looking for other nested <code>&lt;Suspense&gt;</code> boundaries, generates their RSC payloads and then lets Next.js stream the HTML and RSC Payload back to the browser as new chunks. When the browser receives the new chunks, it has the HTML and RSC payload it needs and is ready to replace the fallback element from the DOM with the newly-streamed HTML. And so on.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="399"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg"
			
			sizes="100vw"
			alt="Static HTML and RSC Payload replacing suspended fallback values."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 8. (<a href='https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In Figures 7 and 8, notice how the fallback elements have a unique ID in the form of <code>B:0</code>, <code>B:1</code>, and so on, while the actual components have a similar ID in a similar form: <code>S:0</code> and <code>S:1</code>, and so on.</p>

<p>Along with the first chunk that contains a suspended component’s HTML, the server also ships an <code>$RC</code> function (i.e., <code>completeBoundary</code> from <a href="https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js#L46">React’s source code</a>) that knows how to find the <code>B:0</code> fallback element in the DOM and replace it with the <code>S:0</code> template it received from the server. That’s the “replacer” function that lets us see the component contents when they arrive in the browser.</p>

<p>The entire page eventually finishes loading, chunk by chunk.</p>

<h3 id="lazy-loading-components">Lazy-Loading Components</h3>

<p>If a suspended Server Component contains a lazy-loaded Client Component, Next.js will also send an RSC payload chunk containing instructions on how to fetch and load the lazy-loaded component’s code. This represents a <em>significant performance improvement</em> because the page load isn’t dragged out by JavaScript, which might not even be loaded during that session.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="442"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg"
			
			sizes="100vw"
			alt="Fetching additional JavaScript and CSS files for a lazy-loaded Client Component, as shown in developer tools."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 9. (<a href='https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>At the time I’m writing this, the dynamic method to lazy-load a Client Component in a Server Component in Next.js does not work as you might expect. To effectively lazy-load a Client Component, put it in a <a href="https://github.com/nikolovlazar/rsc-forensics/blob/main/src/components/addToCartWrapper.js">“wrapper” Client Component</a> that uses the <code>dynamic</code> method itself to lazy-load the actual Client Component. The wrapper will be turned into a script that fetches and loads the Client Component’s JavaScript and CSS files at the time they’re needed.</p>

<h3 id="tl-dr">TL;DR</h3>

<p>I know that’s a lot of plates spinning and pieces moving around at various times. What it boils down to, however, is that a page visit triggers Next.js to render as much HTML as it can, using the fallback values for any suspended components, and then sends that to the browser. Meanwhile, Next.js triggers the suspended async components and gets them formatted in HTML and contained in RSC Payloads that are streamed to the browser, one by one, along with an <code>$RC</code> script that knows how to swap things out.</p>

<h2 id="the-page-load-timeline">The Page Load Timeline</h2>

<p>By now, we should have a solid understanding of how RSCs work, how Next.js handles their rendering, and how all the pieces fit together. In this section, we’ll zoom in on what exactly happens when we visit an RSC page in the browser.</p>

<h3 id="the-initial-load">The Initial Load</h3>

<p>As we mentioned in the TL;DR section above, when visiting a page, Next.js will render the initial HTML minus the suspended component and stream it to the browser as part of the first streaming chunks.</p>

<p>To see everything that happens during the page load, we’ll visit the “Performance” tab in Chrome DevTools and click on the “reload” button to reload the page and capture a profile. Here’s what that looks like:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="442"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg"
			
			sizes="100vw"
			alt="Showing the first chunks of HTML streamed at the beginning of the timeline in DevTools."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 10. (<a href='https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>When we zoom in at the very beginning, we can see the first “Parse HTML” span. That’s the server streaming the first chunks of the document to the browser. The browser has just received the initial HTML, which contains the page shell and a few links to resources like fonts, CSS files, and JavaScript. The browser starts to invoke the scripts.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="442"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg"
			
			sizes="100vw"
			alt="The first frames appear, and parts of the page are rendered"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 11. (<a href='https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>After some time, we start to see the page’s first frames appear, along with the initial JavaScript scripts being loaded and hydration taking place. If you look at the frame closely, you’ll see that the whole page shell is rendered, and “loading” components are used in the place where there are suspended Server Components. You might notice that this takes place around 800ms, while the browser started to get the first HTML at  100ms. During those 700ms, the browser is continuously receiving chunks from the server.</p>

<p>Bear in mind that this is a Next.js demo app running locally in development mode, so it’s going to be slower than when it’s running in production mode.</p>

<h3 id="the-suspended-component">The Suspended Component</h3>

<p>Fast forward few seconds and we see another “Parse HTML” span in the page load timeline, but this one it indicates that a suspended Server Component finished loading and is being streamed to the browser.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="442"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg"
			
			sizes="100vw"
			alt="The suspended component’s HTML and RSC Payload are streamed to the browser, as shown in the developer tools Network tab."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 12. (<a href='https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We can also see that a lazy-loaded Client Component is discovered at the same time, and it contains CSS and JavaScript files that need to be fetched. These files weren’t part of the initial bundle because the component isn’t needed until later on; the code is split into their own files.</p>

<p>This way of code-splitting certainly improves the performance of the initial page load. It also makes sure that the Client Component’s code is shipped only if it’s needed. If the Server Component (which acts as the Client Component’s parent component) throws an error, then the Client Component does not load. It doesn’t make sense to load all of its code before we know whether it will load or not.</p>

<p>Figure 12 shows the <code>DOMContentLoaded</code> event is reported at the end of the page load timeline. And, just before that, we can see that the <code>localhost</code> HTTP request comes to an end. That means the server has likely sent the last zero-sized chunk, indicating to the client that the data is fully transferred and that the streaming communication can be closed.</p>

<h3 id="the-end-result">The End Result</h3>

<p>The main <code>localhost</code> HTTP request took around five seconds, but thanks to streaming, we began seeing page contents load much earlier than that. If this was a traditional SSR setup, we would likely be staring at a blank screen for those five seconds before anything arrives. On the other hand, if this was a traditional CSR setup, we would likely have shipped <em>a lot</em> more of JavaScript and put a heavy burden on both the browser and network.</p>

<p>This way, however, the app was fully interactive in those five seconds. We were able to navigate between pages and interact with Client Components that have loaded as part of the initial main bundle. This is a pure win from a user experience standpoint.</p>

<h2 id="conclusion">Conclusion</h2>

<p>RSCs mark a significant evolution in the React ecosystem. They leverage the strengths of server-side and client-side rendering while embracing HTML streaming to speed up content delivery. This approach not only addresses the SEO and loading time issues we experience with CSR but also improves SSR by reducing server load, thus enhancing performance.</p>

<p>I’ve refactored the same RSC app I shared earlier so that it uses the Next.js Page router with SSR. The improvements in RSCs are significant:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg"
			
			sizes="100vw"
			alt="Comparing Next.js Page Router and App Router, side-by-side."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Figure 13. (<a href='https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Looking at these two reports I pulled from Sentry, we can see that streaming allows the page to start loading its resources before the actual request finishes. This significantly improves the Web Vitals metrics, which we see when comparing the two reports.</p>

<p>The conclusion: <strong>Users enjoy faster, more reactive interfaces with an architecture that relies on RSCs.</strong></p>

<p>The RSC architecture introduces two new component types: Server Components and Client Components. This division helps React and the frameworks that rely on it &mdash; like Next.js &mdash; streamline content delivery while maintaining interactivity.</p>

<p>However, this setup also introduces new challenges in areas like state management, authentication, and component architecture. Exploring those challenges is a great topic for another blog post!</p>

<p>Despite these challenges, the benefits of RSCs present a compelling case for their adoption. We definitely will see guides published on how to address RSC’s challenges as they mature, but, in my opinion, they already look like the future of rendering practices in modern web development.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Juan Diego Rodríguez</author><title>The End Of My Gatsby Journey</title><link>https://www.smashingmagazine.com/2024/03/end-of-gatsby-journey/</link><pubDate>Wed, 06 Mar 2024 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/03/end-of-gatsby-journey/</guid><description>&lt;a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/">“Gatsby headaches”&lt;/a> are over. Juan Diego Rodríguez reflects on his decision to stop using Gatsby as his go-to framework. Through a detailed examination of its strengths and weaknesses, he provides valuable insights and alternative options for developers navigating their tooling choices.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/03/end-of-gatsby-journey/" />
              <title>The End Of My Gatsby Journey</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The End Of My Gatsby Journey</h1>
                  
                    
                    <address>Juan Diego Rodríguez</address>
                  
                  <time datetime="2024-03-06T08:00:00&#43;00:00" class="op-published">2024-03-06T08:00:00+00:00</time>
                  <time datetime="2024-03-06T08:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>A fun fact about me is that my birthday is on Valentine’s Day. This year, I wanted to celebrate by launching a simple website that lets people receive anonymous letters through a personal link. The idea came up to me at the beginning of February, so I wanted to finish the project as soon as possible since time was of the essence.</p>

<p>Having that in mind, I decided not to do SSR/SSG with Gatsby for the project but rather go with a single-page application (SPA) using Vite and React &mdash; a rather hard decision considering my extensive experience with Gatsby. Years ago, when I started using React and learning more and more about today’s <a href="https://www.smashingmagazine.com/2024/02/web-development-getting-too-complex/#comments-web-development-getting-too-complex">intricate web landscape</a>, I picked up <a href="https://gatsbyjs.com/">Gatsby.js</a> as my render framework of choice because SSR/SSG was necessary for every website, right?</p>

<p>I used it for <em>everything</em>, from the most basic website to the most over-engineered project. I absolutely loved it and thought it was the best tool, and I was incredibly confident in my decision since I was getting perfect Lighthouse scores in the process.</p>

<p>The years passed, and I found myself constantly fighting with Gatsby plugins, resorting to <em>hacky</em> solutions for them and even spending more time waiting for the server to start. It felt like I was fixing more than making. I even <a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/">started a series for this magazine all about the “Gatsby headaches” I experienced most</a> and how to overcome them.</p>

<p>It was like Gatsby got tougher to use with time because of lots of unaddressed issues: outdated dependencies, cold starts, slow builds, and stale plugins, to name a few. Starting a Gatsby project became tedious for me, and perfect Lighthouse scores couldn’t make up for that.</p>

<p>So, I’ve decided to stop using Gatsby as my go-to framework.</p>

<p>To my surprise, the Vite + React combination I mentioned earlier turned out to be a lot more efficient than I expected while maintaining almost the same great performance measures as Gatsby. It’s a hard conclusion to stomach after years of Gatsby’s loyalty.</p>

<p>I mean, I still think Gatsby is extremely useful for plenty of projects, and I plan on talking about those in a bit. But Gatsby has undergone a series of recent unfortunate events after Netlify acquired it, the impacts of which can be seen in <a href="https://2022.stateofjs.com/en-US/libraries/rendering-frameworks">down-trending results from the most recent State of JavaScript survey</a>. The likelihood of a developer picking up Gatsby again after using it for other projects plummeted from 89% to a meager 38% between 2019 and 2022 alone.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="708"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png"
			
			sizes="100vw"
			alt="A ranking of the rendering frameworks retention."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A ranking of the rendering frameworks retention. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Although Gatsby was still the second most-used rendering framework as recently as 2022 &mdash; we are still expecting results from the 2023 survey &mdash; my prediction is that the decline will continue and dip well below 38%.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="708"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png"
			
			sizes="100vw"
			alt="A ranking of the usage of the rendering framework."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A ranking of the usage of the rendering framework. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Seeing as this is my personal farewell to Gatsby, I wanted to write about where, in my opinion, it went wrong, where it is still useful, and how I am handling my future projects.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="/printed-books/touch-design-for-mobile-interfaces/">Touch Design for Mobile Interfaces</a></strong>, Steven Hoober’s brand-new guide on <strong>designing for mobile</strong> with proven, universal, human-centric guidelines. <strong>400 pages</strong>, jam-packed with in-depth user research and <strong>best practices</strong>.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/14bcab88-b622-47f6-a51d-76b0aa003597/touch-design-book-shop-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/b14658fc-bb2d-41a6-8d1a-70eaaf1b8ec8/touch-design-book-shop-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="gatsby-a-retrospective">Gatsby: A Retrospective</h2>

<p><a href="https://github.com/gatsbyjs/gatsby/releases/tag/0.2.0">Kyle Mathews started working on what would eventually become Gatsby</a> in late 2015. Thanks to its unique data layer and SSG approach, it was hyped for success and achieved a <a href="https://changelog.com/founderstalk/59">$3.8 million funding seed round in 2018</a>. Despite initial doubts, Gatsby remained steadfast in its commitment and became a frontrunner in the <a href="https://css-tricks.com/what-makes-a-site-jamstack/">Jamstack</a> community by consistently enhancing its open-source framework and bringing new and better changes with each version.</p>

<p>So&hellip; where did it all go wrong?</p>

<p>I’d say it was the introduction of <a href="https://www.gatsbyjs.com/docs/reference/cloud/what-is-gatsby-cloud/">Gatsby Cloud</a> in 2019, as Gatsby aimed at generating continuous revenue and solidifying its business model. Many (myself included) pinpoint Gatsby’s downfall to Gatsby Cloud, as it would end up cutting resources from the main framework and even making it harder to host in other cloud providers.</p>

<p>The core framework had been optimized in a way that using Gatsby and Gatsby Cloud together required no additional hosting configurations, which, as a consequence, made deployments in other platforms much more difficult, both by neglecting to provide documentation for third-party deployments and by releasing exclusive features, like <a href="https://www.gatsbyjs.com/blog/what-are-incremental-cloud-builds-on-gatsby/#gatsby-skip-here">incremental builds</a>, that were only available to Gatsby users who had committed to using Gatsby Cloud. In short, hosting projects on anything but Gatsby Cloud felt like a penalty.</p>

<p>As a framework, Gatsby lost users to Next.js, as shown in both surveys and npm trends, while Gatsby Cloud struggled to compete with the likes of Vercel and Netlify; the former <a href="https://www.gatsbyjs.com/blog/gatsby-is-joining-netlify/">acquiring Gatsby in February of 2023</a>.</p>

<blockquote>“It [was] clear after a while that [Gatsby] weren’t winning the framework battle against Vercel, as a general purpose framework [...] And they were probably a bit boxed in by us in terms of building a cloud platform.”<br /><br />&mdash; <a href="https://thenewstack.io/netlify-acquires-gatsby-its-struggling-jamstack-competitor/">Matt Biilmann</a>, Netlify CEO</blockquote>

<p>The Netlify acquisition was the last straw in an already tumbling framework haystack. The migration from Gatsby Cloud to Netlify wasn’t pretty for customers either; some teams were charged 120% more &mdash; or had <a href="https://www.reddit.com/r/webdev/comments/1b14bty/netlify_just_sent_me_a_104k_bill_for_a_simple/">incurred extraneous fees</a> &mdash; after converting from Gatsby Cloud to Netlify, even with the same Gatsby Cloud plan they had! Many key Gatsby Cloud features, specifically incremental builds that reduced build times of small changes from minutes to seconds, were simply no longer available in Netlify, despite Kyle Mathews <a href="https://www.gatsbyjs.com/blog/gatsby-is-joining-netlify/">saying they would be ported over to Netlify</a>:</p>

<blockquote>“Many performance innovations specifically for large, content-heavy websites, preview, and collaboration workflows, will be incorporated into the Netlify platform and, where relevant, made available across frameworks.”<br /><br />&mdash; Kyle Mathews</blockquote>

<p>However, in a Netlify forum thread dated August 2023, a mere six months after the acquisition, a Netlify support engineer contradicted Mathews’s statement, saying <a href="https://answers.netlify.com/t/how-to-enable-gatsby-incremental-build-2023/99488/4">there were no plans to add incremental features in Netlify</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png"
			
			sizes="100vw"
			alt="Netlify forum message from a support engineer."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Netlify forum message from a support engineer. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That left no significant reason to remain with Gatsby. And I think <a href="https://answers.netlify.com/t/how-to-enable-gatsby-incremental-build-2023/99488/4">this comment on the same thread</a> perfectly sums up the community’s collective sentiment:</p>

<blockquote>“Yikes. Huge blow to Gatsby Cloud customers. The incremental build speed was exactly why we switched from Netlify to Gatsby Cloud in the first place. It’s really unfortunate to be forced to migrate while simultaneously introducing a huge regression in performance and experience.”</blockquote>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png"
			
			sizes="100vw"
			alt="Netlify forum message from a user"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Netlify forum message from a user. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Netlify’s acquisition also brought about a <em>company restructuring</em> that substantially reduced the headcount of Gatsby’s engineering team, followed by a complete stop in commit activities. A report in an ominous tweet by Astro co-founder Fred Schott further exacerbated concerns about Gatsby’s future.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="269"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png"
			
			sizes="100vw"
			alt="Fred Schott’s tweet reading, ‘There have been zero commits to the Gatsby repo in the last 24 days.’"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Lennart Jörgens, former full-stack developer at Gatsby and Netlify, replied, insinuating there was only one person left after the layoffs:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="548"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png"
			
			sizes="100vw"
			alt="Lennart Jörgens tweet reading, ‘Don’t expect the one person remaining to do all the work.’"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can see all these factors contributing to Gatsby’s usage downfall in the <a href="https://survey.stackoverflow.co/2023#section-most-popular-technologies-web-frameworks-and-technologies">2023 Stack Overflow survey</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png"
			
			sizes="100vw"
			alt="Stacks overflow ranking of the rendering framework usage."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Stacks overflow ranking of the rendering framework usage. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Biilmann addressed the community’s concerns about Gatsby’s viability in an <a href="https://github.com/gatsbyjs/gatsby/issues/38696#issuecomment-1817064739">open issue from the Gatsby repository</a>:</p>

<blockquote>“While we don’t plan for Gatsby to be where the main innovation in the framework ecosystem takes place, it will be a safe, robust and reliable choice to build production quality websites and e-commerce stores, and will gain new powers by ways of great complementary tools.”<br /><br />&mdash; Matt Biilmann</blockquote>

<p>He also shed light on Gatsby’s future focus:</p>

<blockquote><ul><li>“First, ensure stability, predictability, and good performance.</li>
<li>Second, give it new powers by strong integration with all new tooling that we add to our Composable Web Platform (for more on what’s all that, you can check out our homepage).</li>
<li>Third, make Gatsby more open by decoupling some parts of it that were closely tied to proprietary cloud infrastructure. The already-released Adapters feature is part of that effort.”</li></ul><br />&mdash; Matt Biilmann</blockquote>

<p>So, Gatsby gave up competing against Next.js on innovation, and instead, it will focus on keeping the existing framework clean and steady in its current state. Frankly, this seems like the most reasonable course of action considering today’s state of affairs.</p>

<div class="partners__lead-place"></div>

<h2 id="why-did-people-stop-using-gatsby">Why Did People Stop Using Gatsby?</h2>

<p>Yes, Gatsby Cloud ended abruptly, but as a framework independent of its cloud provider, other aspects encouraged developers to look for alternatives to Gatsby.</p>

<p>As far as I am concerned, Gatsby’s developer experience (<abbr>DX</abbr>) became more of a burden than a help, and there are two main culprits where I lay the blame: <strong>dependency hell</strong> and <strong>slow bundling times</strong>.</p>

<h3 id="dependency-hell">Dependency Hell</h3>

<p>Go ahead and start a new Gatsby project:</p>

<pre><code class="language-shellsession">gatsby new
</code></pre>

<p>After waiting a couple of minutes you will get your brand new Gatsby site. You’d rightly expect to have a clean slate with zero vulnerabilities and outdated dependencies with this out-of-the-box setup, but here’s what you will find in the terminal once you run <code>npm audit</code>:</p>

<pre><code class="language-shellsession">18 vulnerabilities (11 moderate, 6 high, 1 critical)
</code></pre>

<p>That looks concerning &mdash; and it is &mdash; not so much from a security perspective but as an indication of decaying DX. As a static site generator (SSG), Gatsby will, unsurprisingly, deliver a static and safe site that (normally) doesn’t have access to a database or server, making it immune to most cyber attacks. Besides, lots of those vulnerabilities are in the developer tools and never reach the end user. Alas, relying on <code>npm audit</code> to assess your site security is <a href="https://overreacted.io/npm-audit-broken-by-design/">a naive choice at best</a>.</p>

<p>However, those vulnerabilities reveal an underlying issue: the whopping number of dependencies Gatsby uses is 168(!) at the time I’m writing this. For the sake of comparison, Next.js uses 16 dependencies. A lot of Gatsby’s dependencies are outdated, hence the warnings, but trying to update them to their latest versions will likely unleash a dependency hell full of additional npm warnings and errors.</p>

<p>In a <a href="https://www.reddit.com/r/gatsbyjs/comments/woccnn/is_it_possible_to_have_a_gatsby_project_without/">related subreddit</a> from 2022, a user asked, “Is it possible to have a Gatsby site without vulnerabilities?”</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png"
			
			sizes="100vw"
			alt="Reddit comment, ‘Is it possible to have a Gatsby site without vulnerabilities?’"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The real answer is disappointing, but as of March 2024, it remains true.</p>

<p>A Gatsby site should work completely fine, even with that many dependencies, and extending your project shouldn’t be a problem, whether through its plugin ecosystem or other packages. However, when trying to upgrade any existing dependency you will find that you can’t! Or at least you can’t do it without introducing breaking changes to one of the 168 dependencies, many of which rely on outdated versions of other libraries that also cannot be updated.</p>

<p>It’s that inception-like roundabout of dependencies that I call <strong>dependency hell</strong>.</p>

<h3 id="slow-build-and-development-times">Slow Build And Development Times</h3>

<p>To me, one of the most important aspects of choosing a development tool is how comfortable it feels to use it and how fast it is to get a project up and running. <a href="https://www.smashingmagazine.com/2024/02/web-development-getting-too-complex/">As I’ve said before</a>, users don’t care or know what a “tech stack” is or what framework is in use; they want a good-looking website that helps them achieve the task they came for. Many developers don’t even question what tech stack is used on each site they visit; at least, I hope not.</p>

<p>With that in mind, choosing a framework boils down to how efficiently you can use it. If your development server constantly experiences cold starts and crashes and is unable to quickly reflect changes, that’s a poor DX and a signal that there may be a better option.</p>

<p>That’s the main reason I won’t automatically reach for Gatsby from here on out. Installation is no longer a trivial task; the dependencies are firing off warnings, and it takes the development server upwards of 30 seconds to boot. I’ve even found that the longer the server runs, the slower it gets; this happens constantly to me, though I admittedly have not heard similar gripes from other developers. Regardless, I get infuriated having to constantly restart my development server every time I make a change to <code>gatsby-config.js</code>, <code>gatsby-node.js</code> files, or any other data source.</p>

<p>This new reality is particularly painful, knowing that a Vite.js + React setup can start a server within 500ms <a href="https://esbuild.github.io">thanks to the use of esbuild</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="181"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png"
			
			sizes="100vw"
			alt="Esbuild time to craft a production bundle of 10 copies of the three.js library from scratch using default settings."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Esbuild time to craft a production bundle of 10 copies of the three.js library from scratch using default settings. (Image source: <a href='https://esbuild.github.io'>esbuild</a>) (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Running <code>gatsby build</code> gets worse. Build times for larger projects normally take some number of minutes, which is understandable when we consider all of the pages, data sources, and optimizations Gatsby does behind the scenes. However, even a small content edit to a page triggers a full build and deployment process, and the endless waiting is not only exhausting but downright distracting for getting things done. That’s what incremental builds were designed to solve and the reason many people switched from Netlify to Gatsby Cloud when using Gatsby. It’s a shame we no longer have that as an available option.</p>

<p>The moment Gatsby Cloud was discontinued along with incremental builds, the incentives for continuing to use Gatsby became pretty much non-existent. The <strong>slow build times</strong> are simply too costly to the development workflow.</p>

<h2 id="what-gatsby-did-awesomely-well">What Gatsby Did Awesomely Well</h2>

<p>I still believe that Gatsby has awesome things that other rendering frameworks don’t, and that’s why I will keep using it, albeit for specific cases, such as my personal website. It just isn’t my go-to framework for everything, mainly because Gatsby (and the Jamstack) wasn’t meant for every project, even if Gatsby was marketed as a general-purpose framework.</p>

<p>Here’s where I see Gatsby still leading the competition:</p>

<ul>
<li><strong>The GraphQL data layer.</strong><br />
In Gatsby, all the configured data is available in the same place, a <em>data layer</em> that’s easy to access using GraphQL queries in any part of your project. This is by far the best Gatsby feature, and it trivializes the process of building static pages from data, e.g., a blog from a content management system API or documentation from Markdown files.</li>
<li><strong>Client performance.</strong><br />
While Gatsby’s developer experience is questionable, I believe it delivers one of the best user experiences for navigating a website. Static pages and assets deliver the fastest possible load times, and using React Router with pre-rendering of proximate links offers one of the smoothest experiences navigating between pages. We also have to note Gatsby’s amazing image API, which optimizes images to all extents.</li>
<li><strong>The plugin ecosystem (kinda).</strong><br />
There is typically a Gatsby plugin for everything. This is awesome when using a CMS as a data source since you could just install its specific plugin and have all the necessary data in your data layer. However, a lot of plugins went unmaintained and grew outdated, introducing unsolvable dependency issues that come with dependency hell.</li>
</ul>

<p>I briefly glossed over the good parts of Gatsby in contrast to the bad parts. Does that mean that Gatsby has more bad parts? Absolutely not; you just won’t find the bad parts in any documentation. The bad parts also aren’t deal breakers in isolation, but they snowball into a tedious and lengthy developer experience that pushes away its advocates to other solutions or rendering frameworks.</p>

<h3 id="do-we-need-ssr-ssg-for-everything">Do We Need SSR/SSG For Everything?</h3>

<p>I’ll go on record saying that I am not replacing Gatsby with another rendering framework, like Next.js or Remix, but just avoiding them altogether. I’ve found they aren’t actually needed in a lot of cases.</p>

<p>Think, why do we use any type of rendering framework in the first place? I’d say it’s for two main reasons: <strong>crawling bots</strong> and <strong>initial loading time</strong>.</p>

<h4 id="seo-and-crawling-bots">SEO And Crawling Bots</h4>

<p>Most React apps start with a hollow body, only having an empty <code>&lt;div&gt;</code> alongside <code>&lt;script&gt;</code> tags. The JavaScript code then runs in the browser, where React creates the Virtual DOM and injects the rendered user interface into the browser.</p>

<p>Over slow networks, users may notice a white screen before the page is actually rendered, which is just mildly annoying at best (but <a href="https://ericwbailey.design/published/modern-health-frameworks-performance-and-harm/">devastating at worst</a>).</p>

<p>However, search engines like Google and Bing deploy bots that only see an empty page and decide not to crawl the content. Or, if you are linking up a post on social media, you may not get OpenGraph benefits like a link preview.</p>

<pre><code class="language-html">&lt;body&gt;
  &lt;div id="root"&gt;&lt;/div&gt;

  &lt;script type="module" src="/src/main.tsx"&gt;&lt;/script&gt;
&lt;/body&gt;
</code></pre>

<p>This was the case years ago, making SSR/SSG necessary for getting noticed by Google bots. Nowadays, <a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics">Google can run JavaScript</a> and render the content to crawl your website. While using SSR or SSG does make this process faster, not all bots can run JavaScript. It’s a tradeoff you can make for a lot of projects and one you can minimize on your cloud provider by <a href="https://docs.netlify.com/site-deploys/post-processing/prerendering/">pre-rendering</a> your content.</p>

<h4 id="initial-loading-time">Initial Loading Time</h4>

<p>Pre-rendered pages load faster since they deliver static content that relieves the browser from having to run expensive JavaScript.</p>

<p>It’s especially useful when loading pages that are behind authentication; in a client-side rendered (CSR) page, we would need to display a loading state while we check if the user is logged in, while an SSR page can perform the check on the server and send back the correct static content. I have found, however, that this trade-off is an uncompelling argument for using a rendering framework over a CSR React app.</p>

<p>In any case, my SPA built on React + Vite.js gave me a perfect Lighthouse score for the landing page. Pages that fetch data behind authentication resulted in near-perfect Core Web Vitals scores.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="356"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png"
			
			sizes="100vw"
			alt="Near-perfect Lighthouse scores, 99% for performance, 79% for accessibility, 100% for best practices, and 100% for SEO."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Lighthouse scores for the app landing page. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="356"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png"
			
			sizes="100vw"
			alt="Perfect Lighthouse scores of 100% for performance, accessibility, best practices, and SEO."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Lighthouse scores for pages guarded by authentication. (<a href='https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="partners__lead-place"></div>

<h2 id="what-projects-gatsby-is-still-good-for">What Projects Gatsby Is Still Good For</h2>

<p>Gatsby and rendering frameworks are excellent for programmatically creating pages from data and, specifically, for blogs, e-commerce, and documentation.</p>

<p>Don’t be disappointed, though, if it isn’t the right tool for <em>every</em> use case, as that is akin to blaming a screwdriver for not being a good hammer. It still has good uses, though fewer than it could due to all the reasons we discussed before.</p>

<p>But Gatsby is still a useful tool. If you are a Gatsby developer the main reason you’d reach for it is because you <em>know</em> Gatsby. Not using it might be considered an <strong>opportunity cost</strong> in economic terms:</p>

<blockquote>“Opportunity cost is the value of the next-best alternative when a decision is made; it’s what is given up.”</blockquote>

<p>Imagine a student who spends an hour and $30 attending a yoga class the evening before a deadline. The opportunity cost encompasses the time that could have been dedicated to completing the project and the $30 that could have been used for future expenses.</p>

<p>As a Gatsby developer, I could start a new project using another rendering framework like Next.js. Even if Next.js has faster server starts, I would need to factor in my learning curve to use it as efficiently as I do Gatsby. That’s why, for my latest project, I decided to avoid rendering frameworks altogether and use Vite.js + React &mdash; I wanted to avoid the opportunity cost that comes with spending time learning how to use an “unfamiliar” framework.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So, is Gatsby dead? Not at all, or at least I don’t think Netlify will let it go away any time soon. The acquisition and subsequent changes to Gatsby Cloud may have taken a massive toll on the core framework, but Gatsby is very much still breathing, even if the current slow commits pushed to the repo look like it’s barely alive or hibernating.</p>

<p>I will most likely stick to Vite.js + React for my future endeavors and only use rendering frameworks when I actually need them. What are the tradeoffs? Sacrificing negligible page performance in favor of a faster and more pleasant DX that maintains my sanity? I’ll take that deal every day.</p>

<p>And, of course, this is <em>my</em> experience as a long-time Gatsby loyalist. Your experience is likely to differ, so the mileage of everything I’m saying may vary depending on your background using Gatsby on your own projects.</p>

<p>That’s why I’d love for you to comment below: if you see it differently, please tell me! Is your current experience using Gatsby different, better, or worse than it was a year ago? What’s different to you, if anything? It would be awesome to get other perspectives in here, perhaps from someone who has been involved in maintaining the framework.</p>

<h4 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h4>

<ul>
<li><a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/">Gatsby Headaches And How To Cure Them: i18n (Part 1)</a></li>
<li><a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-2/">Gatsby Headaches And How To Cure Them: i18n (Part 2)</a></li>
<li><a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/">Gatsby Headaches: Working With Media (Part 1)</a></li>
<li><a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/">Gatsby Headaches: Working With Media (Part 2)</a></li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Juan Diego Rodríguez</author><title>Making Sense Of “Senseless” JavaScript Features</title><link>https://www.smashingmagazine.com/2023/12/making-sense-of-senseless-javascript-features/</link><pubDate>Thu, 28 Dec 2023 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/12/making-sense-of-senseless-javascript-features/</guid><description>JavaScript may be the most popular client-side language in the world, but it’s far from perfect and not without its quirks. Juan Diego Rodriguez examines several “absurd” JavaScript eccentricities and explains how they made it into the language as well as how to avoid them in your own code.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/12/making-sense-of-senseless-javascript-features/" />
              <title>Making Sense Of “Senseless” JavaScript Features</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Making Sense Of “Senseless” JavaScript Features</h1>
                  
                    
                    <address>Juan Diego Rodríguez</address>
                  
                  <time datetime="2023-12-28T10:00:00&#43;00:00" class="op-published">2023-12-28T10:00:00+00:00</time>
                  <time datetime="2023-12-28T10:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Why does JavaScript have so many eccentricities!? Like, why does <code>0.2 + 0.1</code> equals <code>0.30000000000000004</code>? Or, why does <code>&quot;&quot; == false</code> evaluate to <code>true</code>?</p>

<p>There are a lot of mind-boggling decisions in JavaScript that seem pointless; some are misunderstood, while others are direct missteps in the design. Regardless, it’s worth knowing <em>what</em> these strange things are and <em>why</em> they are in the language. I’ll share what I believe are some of the quirkiest things about JavaScript and make sense of them.</p>

<h2 id="0-1-0-2-and-the-floating-point-format"><code>0.1 + 0.2</code> And The Floating Point Format</h2>

<p>Many of us have mocked JavaScript by writing <code>0.1 + 0.2</code> in the console and watching it resoundingly fail to get <code>0.3</code>, but rather a funny-looking <code>0.30000000000000004</code> value.</p>

<p>What many developers might not know is that the weird result is not really JavaScript’s fault! JavaScript is merely adhering to the <a href="https://ieeexplore.ieee.org/document/8766229"><strong>IEEE Standard for Floating-Point Arithmetic</strong></a> that nearly every other computer and programming language uses to represent numbers.</p>

<p>But what exactly is the Floating-Point Arithmetic?</p>

<p>Computers have to represent numbers in all sizes, from the distance between planets and even between atoms. On paper, it’s easy to write a massive number or a minuscule quantity without worrying about the size it will take. Computers don’t have that luxury since they have to save all kinds of numbers in binary and a small space in memory.</p>

<p>Take an 8-bit integer, for example. In binary, it can hold integers ranging from <code>0</code> to <code>255</code>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg"
			
			sizes="100vw"
			alt="8-bit integers showing 0 and 255."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      8-bit integers showing 0 and 255. (<a href='https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The keyword here is <em>integers</em>. It can’t represent any decimals between them. To fix this, we could add an imaginary decimal point somewhere along our 8-bit so the bits before the point are used to represent the integer part and the rest are used for the decimal part. Since the point is always in the same imaginary spot, it’s called a <strong>fixed point decimal</strong>. But it comes with a great cost since the range is reduced from <strong><code>0</code> to <code>255</code></strong> to exactly <strong><code>0</code> to <code>15.9375</code></strong>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg"
			
			sizes="100vw"
			alt="Decimals with a fixed point."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Decimals with a fixed point. (<a href='https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Having greater precision means sacrificing range, and vice versa. We also have to take into consideration that computers need to please a large number of users with different requirements. An engineer building a bridge doesn’t worry too much if the measurements are off by just a little, say a hundredth of a centimeter. But, on the other hand, that same hundredth of a centimeter can end up costing much more for someone making a microchip. The precision that’s needed is different, and the consequences of a mistake can vary.</p>

<p>Another consideration is the size where numbers are stored in memory since storing long numbers in something like a megabyte isn’t feasible.</p>

<p>The <em>floating-point</em> format was born from this need to represent both large and small quantities with precision and efficiency. It does so in three parts:</p>

<ol>
<li>A single bit that represents whether or not the number is positive or negative (<code>0</code> for positive, <code>1</code> for negative).</li>
<li>A <a href="https://mathworld.wolfram.com/Significand.html">significand</a> or <a href="https://mathworld.wolfram.com/Mantissa.html">mantissa</a> that contains the number’s digits.</li>
<li>An <strong>exponent</strong> specifies where the decimal (or binary) point is placed relative to the beginning of the mantissa, similar to how scientific notation works. Consequently, the point can move around to any position, hence the <em>floating</em> point.</li>
</ol>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg"
			
			sizes="100vw"
			alt="Decimals with a floating point."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Decimals with a floating point. (<a href='https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>An 8-bit floating-point format can represent numbers between <code>0.0078</code> to <code>480</code> (and its negatives), but notice that the floating-point representation can’t represent all of the numbers in that range. It’s impossible since 8 bits can represent only 256 distinct values. Inevitably, many numbers cannot be accurately represented. There are <em>gaps</em> along the range. Computers, of course, work with more bits to increase accuracy and range, commonly with 32-bits and 64-bits, but it’s impossible to represent all numbers accurately, a small price to pay if we consider the range we gain and the memory we save.</p>

<p>The exact dynamics are far more complex, but for now, we only have to understand that while this format allows us to express numbers in a large range, it loses precision (the gaps between representable values get bigger) when they become too big. For example, JavaScript numbers are presented in a double-precision floating-point format, i.e., each number is represented in 64 bits in memory, leaving 53 bits to represent the mantissa. That means JavaScript can only safely represent integers between &ndash;(2<sup>53</sup> &mdash; 1) and 2<sup>53</sup> &mdash; 1 without losing precision. Beyond that, the arithmetic stops making sense. That’s why we have the <code>Number.MAX_SAFE_INTEGER</code> static data property to represent the maximum safe integer in JavaScript, which is (2<sup>53</sup> &mdash; 1) or <code>9007199254740991</code>.</p>

<p>But <code>0.3</code> is obviously below the <code>MAX_SAFE_INTEGER</code> threshold, so why can’t we get it when adding <code>0.1</code> and <code>0.2</code>? The floating-point format struggles with some fractional numbers. It isn’t a problem with the floating-point format, but it certainly is across any number system.</p>

<p>To see this, let’s represent one-third (<sup>1</sup>&frasl;<sub>3</sub>) in base-10.</p>

<pre><code class="language-bash">0.3
</code></pre>

<pre><code class="language-bash">0.33
</code></pre>

<pre><code class="language-bash">0.3333333 [...]
</code></pre>

<p>No matter how many digits we try to write, the result will never be <em>exactly</em> one-third. In the same way, we cannot accurately represent some fractional numbers in base-2 or binary. Take, for example, <code>0.2</code>. We can write it with no problem in base-10, but if we try to write it in binary we get a recurring <code>1001</code> at the end that repeats infinitely.</p>

<pre><code class="language-bash">0.001 1001 1001 1001 1001 1001 10 [...]
</code></pre>

<p>We obviously can’t have an infinitely large number, so at some point, the mantissa has to be truncated, making it impossible not to lose precision in the process. If we try to convert <code>0.2</code> from double-precision floating-point back to base-10, we will see the actual value saved in memory:</p>

<pre><code class="language-bash">0.200000000000000011102230246251565404236316680908203125
</code></pre>

<p>It isn’t 0.2! We cannot represent an awful lot of fractional values &mdash; not only in JavaScript but in almost all computers. So why does running <code>0.2 + 0.2</code> correctly compute <code>0.4</code>? In this case, the imprecision is so small that it gets rounded by Javascript (at the 16<sup>th</sup> decimal), but sometimes the imprecision is enough to escape the rounding mechanism, as is the case with <code>0.2 + 0.1</code>. We can see what’s happening under the hood if we try to sum the actual values of <code>0.1</code> and <code>0.2</code>.</p>

<p>This is the actual value saved when writing <code>0.1</code>:</p>

<pre><code class="language-bash">0.1000000000000000055511151231257827021181583404541015625
</code></pre>

<p>If we manually sum up the actual values of <code>0.1</code> and <code>0.2</code>, we will see the culprit:</p>

<pre><code class="language-bash">0.3000000000000000444089209850062616169452667236328125
</code></pre>

<p>That value is rounded to <code>0.30000000000000004</code>. You can check the real values saved at <a href="https://float.exposed/0x3fb999999999999a">float.exposed</a>.</p>

<p>Floating-point has its known flaws, but its positives outweigh them, and it’s standard around the world. In that sense, it’s actually a relief when all modern systems will give us the same <code>0.30000000000000004</code> result across architectures. It might not be the result you expect, but it’s a result you can predict.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Roll up your sleeves and <strong>boost your UX skills</strong>! Meet <strong><a data-instant href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns</a></strong>&nbsp;🍣, a 10h video library by Vitaly Friedman. <strong>100s of real-life examples</strong> and live UX training. <a href="https://www.youtube.com/watch?v=3mwZztmGgbE">Free preview</a>.</p>
<a data-instant href="https://smart-interface-design-patterns.com/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://smart-interface-design-patterns.com/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/3155f571-450d-42f9-81b4-494aa9b52841/video-course-smart-interface-design-patterns-vitaly-friedman.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/8c98e7f9-8e62-4c43-b833-fc6bf9fea0a9/video-course-smart-interface-design-patterns-vitaly-friedman.jpg"
    alt="Feature Panel"
    width="690"
    height="790"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="type-coercion">Type Coercion</h2>

<p>JavaScript is a dynamically typed language, meaning we don’t have to declare a variable’s type, and it can be changed later in the code.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aI%20find%20dynamically%20typed%20languages%20liberating%20since%20we%20can%20focus%20more%20on%20the%20substance%20of%20the%20code.%0a&url=https://smashingmagazine.com%2f2023%2f12%2fmaking-sense-of-senseless-javascript-features%2f">
      
I find dynamically typed languages liberating since we can focus more on the substance of the code.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>The issue comes from being weakly typed since there are many occasions where the language will try to do an implicit conversion between different types, e.g., from strings to numbers or <em>falsy</em> and <em>truthy</em> values. This is specifically true when using the equality ( <code>==</code>) and plus sign (<code>+</code>) operators. The rules for type coercion are intricate, hard to remember, and even incorrect in certain situations. It’s better to avoid using <code>==</code> and always prefer the strict equality operator (<code>===</code>).</p>

<p>For example, JavaScript will coerce a string to a number when compared with another number:</p>

<pre><code class="language-javascript">console.log("2" == 2); // true
</code></pre>

<p>The inverse applies to the plus sign operator (<code>+</code>). It will try to coerce a number into a string when possible:</p>

<pre><code class="language-javascript">console.log(2 + "2"); // "22"
</code></pre>

<p>That’s why we should only use the plus sign operator (<code>+</code>) if we are sure that the values are numbers. When concatenating strings, it’s better to use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat"><code>concat()</code></a> method or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">template literals</a>.</p>

<p>The reason such coercions are in the language is actually absurd. When JavaScript creator Brendan Eich was asked what <a href="https://thenewstack.io/brendan-eich-on-creating-javascript-in-10-days-and-what-hed-do-differently-today#:~:text=notorious">he would have done differently</a> in JavaScript’s design, his answer was to be more meticulous in the implementations early users of the language wanted:</p>

<blockquote>“I would have avoided some of the compromises that I made when I first got early adopters, and they said, “Can you change this?”<br /><br />&mdash; Brendan Eich</blockquote>

<p>The most glaring example is the reason why we have two equality operators, <code>==</code> and <code>===</code>. When an early JavaScript user prompted his need to compare a number to a string without having to change his code to make a conversion, Brendan added the loose equality operator to satisfy those needs.</p>

<p>There are a lot of other rules governing the loose equality operator (and other statements checking for a condition) that make JavaScript developers scratch their heads. They are complex, tedious, and senseless, so we should avoid the loose equality operator (<code>==</code>) at all costs and replace it with its strict homonym (<code>===</code>).</p>

<p>Why do we have two equality operators in the first place? A lot of factors, but we can point a finger at Guy L. Steele, co-creator of the Scheme programming language. He assured Eich that we could always add another equality operator since there were dialects with five distinct equality operators in the Lisp language! This mentality is dangerous, and nowadays, all features have to be rigorously analyzed because we can always add new features, but once they are in the language, they cannot be removed.</p>

<div class="partners__lead-place"></div>

<h2 id="automatic-semicolon-insertion">Automatic Semicolon Insertion</h2>

<p>When writing code in JavaScript, a semicolon (<code>;</code>) is required at the end of some statements, including:</p>

<ul>
<li><code>var</code>, <code>let</code>, <code>const</code>;</li>
<li>Expression statements;</li>
<li><code>do...while</code>;</li>
<li><code>continue</code>, <code>break</code>, <code>return</code>, <code>throw</code>;</li>
<li><code>debugger</code>;</li>
<li>Class field declarations (public or private);</li>
<li><code>import</code>, <code>export</code>.</li>
</ul>

<p>That said, we don’t necessarily have to insert a semicolon every time since JavaScript can automatically insert semicolons in a process unsurprisingly known as <strong>Automatic Semicolon Insertion</strong> (ASI). It was intended to make coding easier for beginners who didn’t know where a semicolon was needed, but it isn’t a reliable feature, and we should stick to explicitly typing where a semicolon goes. Linters and formatters add a semicolon where ASI would, but they aren’t completely reliable either.</p>

<p>ASI can make some code work, but most of the time it doesn’t. Take the following code:</p>

<pre><code class="language-javascript">const a = 1
(1).toString()

const b = 1
[1, 2, 3].forEach(console.log)
</code></pre>

<p>You can probably see where the semicolons go, and if we formatted it correctly, it would end up as:</p>

<pre><code class="language-javascript">const a = 1;

(1).toString();

const b = 1;

[(1, 2, 3)].forEach(console.log);
</code></pre>

<p>But if we feed the prior code directly to JavaScript, all kinds of exceptions would be thrown since it would be the same as writing this:</p>

<pre><code class="language-javascript">const a = 1(1).toString();

const b = (1)[(1, 2, 3)].forEach(console.log);
</code></pre>

<p>In conclusion, know your semicolons.</p>

<h2 id="why-so-many-bottom-values">Why So Many Bottom Values?</h2>

<p>The term “bottom” is often used to represent a value that does not exist or is undefined. But why do we have two kinds of bottom values in JavaScript?</p>

<p>Everything in JavaScript can be considered an object, except the two bottom values <code>null</code> and <code>undefined</code> (despite <code>typeof null</code> returning <code>object</code>). Attempting to get a property value from them raises an exception.</p>

<p>Note that, strictly speaking, <strong>all primitive values aren’t objects</strong>. But only <code>null</code> and <code>undefined</code> aren’t subjected to <a href="https://stackoverflow.com/questions/34067261/is-boxing-coercion-in-javascript"><em>boxing</em></a>.</p>

<p>We can even think of <code>NaN</code> as a third bottom value that represents the absence of a number. The abundance of bottom values should be regarded as a design error. There isn’t a straightforward reason that explains the existence of two bottom values, but we can see a difference in how JavaScript employs them.</p>

<p><code>undefined</code> is the bottom value that JavaScript uses by default, so it’s considered good practice to use it exclusively in your code. When we define a variable without an initial value, attempting to retrieve it assigns the <code>undefined</code> value. The same thing happens when we try to access a non-existing property from an object. To match JavaScript’s behavior as closely as possible, use <code>undefined</code> to denote an existing property or variable that doesn’t have a value.</p>

<p>On the other hand, <code>null</code> is used to represent the absence of an object (hence, its <code>typeof</code> returns an <code>object</code> even though it isn’t). However, this is considered a design blunder because <code>undefined</code> could fulfill its purposes as effectively. It’s used by JavaScript to denote the end of a recursive data structure. More specifically, it’s used in the prototype chain to denote its end. Most of the time, you can use <code>undefined</code> over <code>null</code>, but there are some occasions where only <code>null</code> can be used, as is the case with <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create"><code>Object.create</code></a> in which we can only create an object without a prototype passing <code>null</code>; using <code>undefined</code> returns a <code>TypeError</code>.</p>

<p><code>null</code> and <code>undefined</code> both suffer from the path problem. When trying to access a property from a bottom value &mdash; as if they were objects &mdash; exceptions are raised.</p>

<pre><code class="language-javascript">let user;

let userName = user.name; // Uncaught TypeError

let userNick = user.name.nick; // Uncaught TypeError
</code></pre>

<p>There is no way around this unless we check for each property value before trying to access the next one, either using the logical AND (<code>&amp;&amp;</code>) or optional chaining (<code>?</code>).</p>

<pre><code class="language-javascript">let user;

let userName = user?.name;

let userNick = user && user.name && user.name.nick;

console.log(userName); // undefined

console.log(userNick); // undefined
</code></pre>

<p>I said that <code>NaN</code> can be considered a bottom value, but it has its own confusing place in JavaScript since it represents numbers that aren’t actual numbers, usually due to a failed string-to-number conversion (which is another reason to avoid it). <code>NaN</code> has its own shenanigans because it isn’t equal to itself! To test if a value is <code>NaN</code> or not, use <code>Number.isNaN()</code>.</p>

<p>We can check for all three bottom values with the following test:</p>

<pre><code class="language-javascript">function stringifyBottom(bottomValue) {
  if (bottomValue === undefined) {
    return "undefined";
  }

  if (bottomValue === null) {
    return "null";
  }

  if (Number.isNaN(bottomValue)) {
    return "NaN";
  }
}
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="increment-and-decrement">Increment (<code>++</code>) And Decrement (<code>--</code>)</h2>

<p>As developers, we tend to spend more time reading code rather than writing it. Whether we are reading documentation, reviewing someone else’s work, or checking our own, <strong>code readability will increase our productivity over brevity</strong>. In other words, readability saves time in the long run.</p>

<p>That’s why I prefer using <code>+ 1</code> or <code>- 1</code> rather than the increment (<code>++</code>) and decrement (<code>--</code>) operators.</p>

<p>It’s illogical to have a different syntax exclusively for incrementing a value by one in addition to having a pre-increment form and a post-increment form, depending on where the operator is placed. It is very easy to get them reversed, and that can be difficult to debug. They shouldn’t have a place in your code or even in the language as a whole when we consider where the increment operators come from.</p>

<p>As we saw in a <a href="https://www.smashingmagazine.com/2023/12/marketing-changed-oop-javascript/">previous article</a>, JavaScript syntax is heavily inspired by the C language, which uses pointer variables. Pointer variables were designed to store the memory addresses of other variables, enabling dynamic memory allocation and manipulation. The <code>++</code> and <code>--</code> operators were originally crafted for the specific purpose of advancing or stepping back through memory locations.</p>

<p>Nowadays, pointer arithmetic has been proven harmful and can cause accidental access to memory locations beyond the intended boundaries of arrays or buffers, leading to memory errors, a notorious source of bugs and vulnerabilities. Regardless, the syntax made its way to JavaScript and remains there today.</p>

<p>While the use of <code>++</code> and <code>--</code> remains a standard among developers, an argument for readability can be made. Opting for <code>+ 1</code> or <code>- 1</code> over <code>++</code> and <code>--</code> not only aligns with the principles of clarity and explicitness but also avoids having to deal with its pre-increment form and post-increment form.</p>

<p>Overall, it isn’t a life-or-death situation but a nice way to make your code more readable.</p>

<h2 id="conclusion">Conclusion</h2>

<p>JavaScript’s seemingly senseless features often arise from historical decisions, compromises, and attempts to cater to all needs. Unfortunately, it’s impossible to make everyone happy, and JavaScript is no exception.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aJavaScript%20doesn%e2%80%99t%20have%20the%20responsibility%20to%20accommodate%20all%20developers,%20but%20each%20developer%20has%20the%20responsibility%20to%20understand%20the%20language%20and%20embrace%20its%20strengths%20while%20being%20mindful%20of%20its%20quirks.%0a&url=https://smashingmagazine.com%2f2023%2f12%2fmaking-sense-of-senseless-javascript-features%2f">
      
JavaScript doesn’t have the responsibility to accommodate all developers, but each developer has the responsibility to understand the language and embrace its strengths while being mindful of its quirks.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>I hope you find it worth your while to keep learning more and more about JavaScript and its history to get a grasp of its misunderstood features and questionable decisions. Take its amazing prototypal nature, for example. It was obscured during development or blunders like the <code>this</code> keyword and its multipurpose behavior.</p>

<p>Either way, I encourage every developer to research and learn more about the language. And if you’re interested, I go a bit deeper into questionable areas of JavaScript’s design in <a href="https://www.smashingmagazine.com/2023/12/marketing-changed-oop-javascript/">another article published here on Smashing Magazine</a>!</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Facundo Giuliani</author><title>An Introduction To Full Stack Composability</title><link>https://www.smashingmagazine.com/2023/11/introduction-full-stack-composability/</link><pubDate>Thu, 23 Nov 2023 20:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/11/introduction-full-stack-composability/</guid><description>A well-designed composable system should not only consider the technical aspects but also take into account the nature of the content it handles. To help us with that, we can use a Headless Content Management system such as &lt;a href="https://www.storyblok.com/?utm_source=smashing&amp;amp;utm_medium=sponsor&amp;amp;utm_campaign=DGM_DEV_SMA_PLG&amp;amp;utm_content=full-stack-composability-with-storyblok/">Storyblok&lt;/a>.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/11/introduction-full-stack-composability/" />
              <title>An Introduction To Full Stack Composability</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>An Introduction To Full Stack Composability</h1>
                  
                    
                    <address>Facundo Giuliani</address>
                  
                  <time datetime="2023-11-23T20:00:00&#43;00:00" class="op-published">2023-11-23T20:00:00+00:00</time>
                  <time datetime="2023-11-23T20:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Storyblok</b></p>
                

<p>Composability is not only about building a design system. We should create and manage reusable components in the frontend and the UX of our website, as well as coordinate with the backend and the content itself.</p>

<p>In this article, we’ll discuss how to go through both the server and the client sides of our projects and how to align with the content that we’ll manage. We’ll also compare how to <strong>implement composable logic into our code</strong> using different approaches provided by React-based frameworks like Remix and Next.js.</p>

<h2 id="composable-architecture">Composable Architecture</h2>

<p>In the dynamic landscape of web development, the concept of composability has emerged as a key player in crafting scalable, maintainable, and efficient systems. It goes beyond merely constructing design systems; it encompasses the creation and management of reusable components across the entire spectrum of web development, from frontend UX to backend coordination and content management.</p>

<p>Composability is the art of <strong>building systems in a modular and flexible way</strong>. It emphasizes creating components that are not only reusable but can seamlessly fit together, forming a cohesive and adaptable architecture. This approach not only enhances the development process but also promotes consistency and scalability across projects.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="518"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png"
			
			sizes="100vw"
			alt="Composable Architecture"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Composable Architecture (<a href='https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We define “composable architecture” as the idea of building software systems from small, independent components that you can combine to form a complete system. Think of a system as a set of <a href="https://www.lego.com/">LEGO</a> pieces. Putting them together, you can build cool structures, figures, and other creations. But the cool thing about those blocks is that you can exchange them, <strong>reuse them for other creations</strong>, and add new pieces to your existing models.</p>

<h2 id="parts-of-a-composable-architecture">Parts Of A Composable Architecture</h2>

<p>To manage a composable architecture for our projects, we have to connect two parts:</p>

<h3 id="modular-components">Modular Components</h3>

<p>Break down the system into independent, self-contained modules or components. Modular components can be developed, tested, and updated independently, promoting reusability and easier maintenance.</p>

<p>When we talk about modular components, we are referring to units like:</p>

<ul>
<li><strong>Microservices</strong><br />
The architectural style for developing software applications as a set of small, independent services that communicate with each other through well-defined APIs. The application is broken down into a collection of loosely coupled and independently deployable services, each responsible for a specific business capability.</li>
<li><strong>Headless Applications</strong><br />
In a headless architecture, the application’s logic and functionality are decoupled from the presentation layer, allowing it to function independently of a specific user interface.</li>
<li><strong>Packaged Business Capabilities (PBC)</strong><br />
A set of activities, products, and services bundled together and offered as a complete solution. It is a very common concept in the e-commerce environment.</li>
</ul>

<h3 id="apis">APIs</h3>

<p>As the components of our architecture can manage different types of data, processes, and tasks of different natures, they need a common language to communicate between them. Components should expose consistent and well-documented APIs (Application Programming Interfaces). An API is a set of rules and protocols that allows one software application to interact with another. APIs define the methods and data formats that applications can use to communicate with each other.</p>

<h2 id="benefits-of-a-composable-architecture">Benefits Of A Composable Architecture</h2>

<p>When applying a composable approach to the architecture of our projects, we will see some benefits and advantages:</p>

<ul>
<li><strong>Easy to reuse.</strong><br />
Components are designed to be modular and independent. This makes it easy to reuse them in different parts of the system or entirely different systems. Reusability can significantly reduce development time and effort, as well as improve consistency across different projects.</li>
<li><strong>Easy to scale.</strong><br />
When the demand for a particular service or functionality increases, you can scale the system by adding more instances of the relevant components without affecting the entire architecture. This scalability is essential for handling growing workloads and adapting to changing business requirements.</li>
<li><strong>Easy to maintain.</strong><br />
Each component is self-contained. If there’s a need to update or fix a specific feature, it can be done without affecting the entire system. This modularity makes it easier to identify, isolate, and address issues, reducing the impact of maintenance activities on the overall system.</li>
<li><strong>Independence from vendors.</strong><br />
This reduces the dependence on specific vendors for components, making it easier to switch or upgrade individual parts without disrupting the entire system.</li>
<li><strong>Faster development and iteration.</strong><br />
Development teams can work on different components concurrently. This parallel development accelerates the overall development process. Additionally, updates and improvements can be rolled out independently.</li>
</ul>

<h2 id="the-mach-architecture">The MACH Architecture</h2>

<p>An example of composability is what is called the <a href="https://machalliance.org/">MACH architecture</a>. The MACH acronym breaks down into Microservices, API-first, Cloud-native, and Headless. This approach is focused on applying composability in a way that allows you to mold the entire ecosystem of your projects and organization to make it align with business needs.</p>

<p>One of the main ideas of the MACH approach is to let marketers, designers, and front-end developers do their thing without having to worry about the backend in the process. They can tweak the look and feel on the fly, run tests, and <strong>adapt to what customers want</strong> without slowing down the whole operation.</p>

<p>With MACH architecture, getting to an MVP (minimum viable product) is like a rocket ride. Developers can whip up quick prototypes, and businesses can test out their big ideas before going all-in.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="518"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png"
			
			sizes="100vw"
			alt=""
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      An example of MACH approach: A Headless CMS (<a href='https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png'>Large preview</a>)” alt=” An example of MACH approach: A Headless CMS
    </figcaption>
  
</figure>

<p>MACH also helps to resolve poor web performance, using improved services and tools that apply specifically to each one of the different components of your organization’s ecosystem. For example, in the e-commerce game, time is money &mdash; those old-school sites using monolithic platforms lose sales to the speedy and flexible ones built with a composable approach. MACH lets you create a custom IT setup using the hottest tech out there.</p>

<h2 id="composability-with-react">Composability With React</h2>

<p>We can dive deeper and transfer the same composability concept to the implementation of the user interface and experience of our application. React encourages developers to break down user interfaces into small, reusable components that can be composed together to <strong>create complex UIs</strong>. Each React component is designed to encapsulate a specific piece of functionality or user interface element, promoting a modular and composable architecture. This approach facilitates code reuse, as components can be easily shared and integrated into various parts of an application.</p>

<p>React’s component-based architecture simplifies the development process by allowing developers to focus on building small, self-contained units of functionality. These components can then be combined to create more sophisticated features and build new “higher-level” components. The reusability of React components is a key benefit, as it promotes a <strong>more efficient and maintainable codebase</strong>. With this idea in mind, we can build design systems for our projects, as well as following approaches like the <a href="https://bradfrost.com/blog/post/atomic-web-design/">Atomic Design principle</a>.</p>

<h2 id="going-full-stack">Going Full Stack</h2>

<p>But we can go even further and apply the composable approach to our backend and server-side logic, too. React-based frameworks, such as Remix and Next.js, provide powerful tools for implementing composability in web applications while getting the best from different rendering approaches, such as <a href="https://www.smashingmagazine.com/2020/07/differences-static-generated-sites-server-side-rendered-apps/">server-side rendering and static generation</a>. Let’s see a couple of concepts applied in both Next.js and Remix that can help us implement server logic while keeping the composability of our code.</p>

<h2 id="full-stack-composability-with-next-js-react-server-components">Full Stack Composability with Next.js: React Server Components</h2>

<p><a href="https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components">React Server Components</a> were introduced by the React team to address challenges related to server-rendered content and enhance the composability of React applications. Composability, in the context of React Server Components, refers to the ability to break down a user interface into smaller, reusable units that can be efficiently managed on the server side. React Server Components take the idea of composability a step further by <strong>allowing certain components to be rendered</strong> and processed on the server rather than the client. This is particularly useful for large-scale applications where rendering everything on the client side might lead to performance bottlenecks.</p>

<p><a href="https://nextjs.org/">Next.js</a>, a React-based framework, includes React Server Components as the default approach when creating and managing components on a project, <a href="https://nextjs.org/blog/next-13#server-components">since its version 13</a>. Some of the benefits identified when using <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">React Server Components</a> are:</p>

<ul>
<li>Move data fetching operations to the server, closer to the data source. This also reduces the amount of requests the client needs to make while using the power of processing the web servers offer, compared to the processing capacity of the client devices.</li>
<li>Better security when managing and rendering content.</li>
<li>Better caching strategies.</li>
<li>Improved Initial Page Load and First Contentful Paint (FCP).</li>
<li>Reduce bundle sizes.</li>
</ul>

<h2 id="full-stack-composability-with-remix-full-stack-components">Full Stack Composability With Remix: Full Stack Components</h2>

<p>One of Remix’s key principles is the ability to create composable route components, allowing developers to build complex user interfaces by combining smaller, self-contained pieces.</p>

<p><a href="https://twitter.com/kentcdodds">Kent C. Dodds</a>, a very valuable member of the dev community, introduced the concept of <a href="https://www.epicweb.dev/full-stack-components">Full Stack Components</a> to describe the way the <a href="https://remix.run/">Remix</a> framework allows the developers to create components, encapsulating the data fetching and processing they require and involve, in a single file or module that manages <strong>both client-side and server-side logic</strong>.</p>

<p>With the help of <a href="https://remix.run/docs/en/main/route/loader">Loader</a> and <a href="https://remix.run/docs/en/main/route/action">Action</a> functions, in addition to the <a href="https://remix.run/docs/en/1.19.3/guides/resource-routes">Resource Routes</a>, Remix allows developers to add server-side logic, like fetching or data mutations, on the same file where we define the visual representation of the component.</p>

<h2 id="aligning-with-the-content">Aligning With The Content</h2>

<p>Finally, one of the challenges in web development is aligning the composability of our architecture and presentation layer with the content they manage. A well-designed composable system should not only consider the technical aspects but also take into account the nature of the content it handles. To help us with that, we can use a Headless Content Management system such as Storyblok.</p>

<h2 id="storyblok-headless-cms-with-a-composable-approach">Storyblok: Headless CMS With A Composable Approach</h2>

<p><a href="https://storyblok.com/?utm_source=smashing&amp;utm_medium=sponsor&amp;utm_campaign=DGM_DEV_SMA_PLG&amp;utm_content=full-stack-composability-with-storyblok/">Storyblok</a> is a Headless Content Management System with many features and capabilities that help content editors, marketers, and other types of users to create and manage content efficiently. The content created with Storyblok (or any Headless CMS) <strong>can be presented in any way</strong>, as these platforms don’t force the developers to use a particular presentation layer logic.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="518"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png"
			
			sizes="100vw"
			alt="Storyblok"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Storyblok allows you to create and reuse content structures. You can even create and nest components without limits, fill them with content, and customize them to your needs. (<a href='https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The advantage of Storyblok, speaking about composability, is the <a href="https://www.storyblok.com/docs/guide/essentials/content-structures#component?utm_source=smashing&amp;utm_medium=sponsor&amp;utm_campaign=DGM_DEV_SMA_PLG&amp;utm_content=full-stack-composability-with-storyblok/">component approach</a> it uses to manage the content structures. Because of its Headless nature, Storyblok allows you to use the created components (or “blocks”, as they are called on the platform) with any technology or framework. Linking to the previous topic, you can create component structures to manage content in Storyblok while <strong>managing their visual representation</strong> with React components on the client-side (or server-side) of your application.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Composability is a powerful paradigm that transforms the way we approach web development. By fostering modularity, reusability, and adaptability, a composable architecture lays the groundwork for building robust and scalable web applications. As we navigate through both server and client sides, aligning with content management, developers can <strong>harness the full potential of composability</strong> in order to create a cohesive and efficient web development ecosystem.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Juan Diego Rodríguez</author><title>Gatsby Headaches: Working With Media (Part 2)</title><link>https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/</link><pubDate>Mon, 16 Oct 2023 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/</guid><description>In the final part of this two-part series on solving headaches when working with media files in Gatsby projects, Juan Rodriguez demonstrates strategies and techniques for handling various types of documents, including Markdown files, PDFs, and 3D models.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/" />
              <title>Gatsby Headaches: Working With Media (Part 2)</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Gatsby Headaches: Working With Media (Part 2)</h1>
                  
                    
                    <address>Juan Diego Rodríguez</address>
                  
                  <time datetime="2023-10-16T13:00:00&#43;00:00" class="op-published">2023-10-16T13:00:00+00:00</time>
                  <time datetime="2023-10-16T13:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Gatsby is a true Jamstack framework. It works with React-powered components that consume APIs before optimizing and bundling everything to serve as static files with bits of reactivity. That includes media files, like images, video, and audio.</p>

<p>The problem is that there’s no “one” way to handle media in a Gatsby project. We have plugins for everything, from making queries off your local filesystem and compressing files to inlining SVGs and serving images in the responsive image format.</p>

<p>Which plugins should be used for certain types of media? How about certain use cases for certain types of media? That’s where you might encounter headaches because there are many plugins &mdash; some official and some not &mdash; that are capable of handling one or more use cases &mdash; some outdated and some not.</p>

<p>That is what this brief two-part series is about. In <a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/">Part 1</a>, we discussed various strategies and techniques for handling images, video, and audio in a Gatsby project.</p>

<p>This time, in Part 2, we are covering a different type of media we commonly encounter: <strong>documents</strong>. Specifically, we will tackle considerations for Gatsby projects that make use of Markdown and PDF files. And before wrapping up, we will also demonstrate an approach for using 3D models.</p>

<h2 id="solving-markdown-headaches-in-gatsby">Solving Markdown Headaches In Gatsby</h2>

<p>In Gatsby, Markdown files are commonly used to programmatically create pages, such as blog posts. You can write content in Markdown, parse it into your GraphQL data layer, source it into your components, and then bundle it as HTML static files during the build process.</p>

<p>Let’s learn how to load, query, and handle the Markdown for an existing page in Gatsby.</p>

<h3 id="loading-and-querying-markdown-from-graphql">Loading And Querying Markdown From GraphQL</h3>

<p>The first step on your Gatsby project is to load the project’s Markdown files to the GraphQL data layer. We can do this using the <code>gatsby-source-filesystem</code> plugin we used to query the local filesystem for image files in <a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/">Part 1</a> of this series.</p>

<pre><code class="language-bash">npm i gatsby-source-filesystem
</code></pre>

<p>In <code>gatsby-config.js</code>, we declare the folder where Markdown files will be saved in the project:</p>

<pre><code class="language-javascript">module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `assets`,
        path: `${ &#95;&#95;dirname }/src/assets`,
      },
    },
  ],
};
</code></pre>

<p>Let’s say that we have the following Markdown file located in the project’s <code>./src/assets</code> directory:</p>

<div class="break-out">
<pre><code class="language-markdown">---
title: sample-markdown-file
date: 2023-07-29
---

&#35; Sample Markdown File

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed consectetur imperdiet urna, vitae pellentesque mauris sollicitudin at. Sed id semper ex, ac vestibulum nunc. Etiam ,

![A beautiful forest!](/forest.jpg "Forest trail")

```bash
lorem ipsum dolor sit
```

&#35;&#35; Subsection

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed consectetur imperdiet urna, vitae pellentesque mauris sollicitudin at. Sed id semper ex, ac vestibulum nunc. Etiam efficitur, nunc nec placerat dignissim, ipsum ante ultrices ante, sed luctus nisl felis eget ligula. Proin sed quam auctor, posuere enim eu, vulputate felis. Sed egestas, tortor
</code></pre>
</div>

<p>This example consists of two main sections: the <strong>frontmatter</strong> and <strong>body</strong>. It is a common structure for Markdown files.</p>

<ul>
<li><strong>Frontmatter</strong><br />
Enclosed in triple dashes (<code>---</code>), this is an optional section at the beginning of a Markdown file that contains metadata and configuration settings for the document. In our example, the frontmatter contains information about the page’s <code>title</code> and <code>date</code>, which Gatsby can use as GraphQL arguments.</li>
<li><strong>Body</strong><br />
This is the content that makes up the page’s main body content.</li>
</ul>

<p>We can use the <a href="https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/"><code>gatsby-transformer-remark</code></a> plugin to parse Markdown files to a GraphQL data layer. Once it is installed, we will need to register it in the project’s <code>gatsby-config.js</code> file:</p>

<pre><code class="language-javascript">module.exports = {
  plugins: [
    {
      resolve: `gatsby-transformer-remark`,
      options: { },
    },
  ],
};
</code></pre>

<p>Restart the development server and navigate to <code>http://localhost:8000/___graphql</code> in the browser. Here, we can play around with Gatsby’s data layer and check our Markdown file above by making a query using the <code>title</code> property (<code>sample-markdown-file</code>) in the frontmatter:</p>

<div class="break-out">
<pre><code class="language-javascript">query {
  markdownRemark(frontmatter: { title: { eq: "sample-markdown-file" } }) {
    html
  }
}
</code></pre>
</div>

<p>This should return the following result:</p>

<div class="break-out">
<pre><code class="language-javascript">{
  "data": {
    "markdownRemark": {
      "html": "&lt;h1&gt;Sample Markdown File&lt;/h1&gt;\n&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed consectetur imperdiet urna, vitae pellentesque mauris sollicitudin at."
      // etc.
    }
  },
  "extensions": {}
}
</code></pre>
</div>

<p>Notice that the <strong>content in the response is formatted in HTML</strong>. We can also query the original body as <code>rawMarkdownBody</code> or any of the frontmatter attributes.</p>

<p>Next, let’s turn our attention to approaches for handling Markdown content once it has been queried.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h3 id="using-dangerouslysetinnerhtml">Using <code>DangerouslySetInnerHTML</code></h3>

<p><code>dangerouslySetInnerHTML</code> is a React feature that injects raw HTML content into a component’s rendered output by overriding the <code>innerHTML</code> property of the DOM node. It’s considered <em>dangerous</em> since it essentially bypasses React’s built-in mechanisms for rendering and sanitizing content, opening up the possibility of <strong>cross-site scripting (XSS)</strong> attacks without paying special attention.</p>

<p>That said, if you need to render HTML content dynamically but want to avoid the risks associated with <code>dangerouslySetInnerHTML</code>, consider using libraries that sanitize HTML input <em>before</em> rendering it, such as <a href="https://github.com/cure53/DOMPurify"><code>dompurify</code></a>.</p>

<p>The <code>dangerouslySetInnerHTML</code> prop takes an <code>__html</code> object with a single key that should contain the raw HTML content. Here’s an example:</p>

<pre><code class="language-javascript">const DangerousComponent = () =&gt; {
  const rawHTML = "&lt;p&gt;This is &lt;em&gt;dangerous&lt;/em&gt; content!&lt;/p&gt;";

  return &lt;div dangerouslySetInnerHTML={ { &#95;&#95;html: rawHTML } } /&gt;;
};
</code></pre>

<p>To display Markdown using <code>dangerouslySetInnerHTML</code> in a Gatsby project, we need first to query the HTML string using Gatsby’s <code>useStaticQuery</code> hook:</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { useStaticQuery, graphql } from "gatsby";

const DangerouslySetInnerHTML = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      markdownRemark(frontmatter: { title: { eq: "sample-markdown-file" } }) {
        html
      }
    }
  `);

  return &lt;div&gt;&lt;/div&gt;;
};
</code></pre>
</div>

<p>Now, the <code>html</code> property can be injected into the <code>dangerouslySetInnerHTML</code> prop.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { useStaticQuery, graphql } from "gatsby";

const DangerouslySetInnerHTML = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      markdownRemark(frontmatter: { title: { eq: "sample-markdown-file" } }) {
        html
      }
    }
  `);

  const markup = { &#95;&#95;html: data.markdownRemark.html };

  return &lt;div dangerouslySetInnerHTML={ markup }&gt;&lt;/div&gt;;
};
</code></pre>
</div>

<p>This might look OK at first, but if we were to open the browser to view the content, we would notice that the image declared in the Markdown file is missing from the output. We never told Gatsby to parse it. We do have two options to include it in the query, each with pros and cons:</p>

<ol>
<li><strong>Use a plugin to parse Markdown images.</strong><br />
The <code>gatsby-remark-images</code> plugin is capable of processing Markdown images, making them available when querying the Markdown from the data layer. The main downside is the extra configuration it requires to set and render the files. Besides, Markdown images parsed with this plugin only will be available as HTML, so we would need to select a package that can render HTML content into React components, such as <a href="https://www.npmjs.com/package/rehype-react"><code>rehype-react</code></a>.</li>
<li><strong>Save images in the <code>static</code> folder.</strong><br />
The <code>/static</code> folder at the root of a Gatsby project can store assets that won’t be parsed by webpack but will be available in the <code>public</code> directory. Knowing this, we can point Markdown images to the <code>/static</code> directory, and they will be available anywhere in the client. The disadvantage? We are unable to leverage Gatsby’s image optimization features to minimize the overall size of the bundled package in the build process.</li>
</ol>

<p><strong>The <code>gatsby-remark-images</code> approach is probably most suited for larger projects since it is more manageable</strong> than saving all Markdown images in the <code>/static</code> folder.</p>

<p>Let’s assume that we have decided to go with the second approach of saving images to the <code>/static</code> folder. To reference an image in the <code>/static</code> directory, we just point to the filename without any special argument on the path.</p>

<pre><code class="language-javascript">const StaticImage = () =&gt; {
  return &lt;img src={ "/desert.png" } alt="Desert" /&gt;;
};
</code></pre>

<h3 id="react-markdown"><code>react-markdown</code></h3>

<p>The <code>react-markdown</code> package provides a component that renders markdown into React components, avoiding the risks of using <code>dangerouslySetInnerHTML</code>. The component uses a syntax tree to build the virtual DOM, which allows for updating only the changing DOM instead of completely overwriting it. And since it uses <a href="https://github.com/remarkjs/remark"><code>remark</code></a>, we can combine <code>react-markdown</code> with <code>remark</code>’s vast <a href="https://github.com/remarkjs/remark/blob/main/doc/plugins.md">plugin ecosystem</a>.</p>

<p>Let’s install the package:</p>

<pre><code class="language-bash">npm i react-markdown
</code></pre>

<p>Next, we replace our prior example with the <code>ReactMarkdown</code> component. However, instead of querying for the <code>html</code> property this time, we will query for <code>rawMarkdownBody</code> and then pass the result to <code>ReactMarkdown</code> to render it in the DOM.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import ReactMarkdown from "react-markdown";
import { useStaticQuery, graphql } from "gatsby";

const MarkdownReact = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      markdownRemark(frontmatter: { title: { eq: "sample-markdown-file" } }) {
        rawMarkdownBody
      }
    }
  `);

  return &lt;ReactMarkdown&gt;{data.markdownRemark.rawMarkdownBody}&lt;/ReactMarkdown&gt;;
};
</code></pre>
</div>

<h3 id="markdown-to-jsx"><code>markdown-to-jsx</code></h3>

<p><code>markdown-to-jsx</code> is the most popular Markdown component &mdash; and the lightest since it comes without any dependencies. It’s an excellent tool to consider when aiming for performance, and it does not require <code>remark</code>’s plugin ecosystem. The plugin works much the same as the <code>react-markdown</code> package, only this time, we import a <code>Markdown</code> component instead of <code>ReactMarkdown</code>.</p>

<pre><code class="language-bash">npm i markdown-to-jsx
</code></pre>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import Markdown from "markdown-to-jsx";
import { useStaticQuery, graphql } from "gatsby";

const MarkdownToJSX = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      markdownRemark(frontmatter: { title: { eq: "sample-markdown-file" } }) {
        rawMarkdownBody
      }
    }
  `);

  return &lt;Markdown&gt; { data.markdownRemark.rawMarkdownBody }&lt;/Markdown&gt;;
};
</code></pre>
</div>

<p>We have taken raw Markdown and parsed it as JSX. But what if we don’t necessarily want to parse it at all? We will look at that use case next.</p>

<h3 id="react-md-editor"><code>react-md-editor</code></h3>

<p>Let’s assume for a moment that we are creating a lightweight CMS and want to give users the option to write posts in Markdown. In this case, instead of parsing the Markdown to HTML, we need to query it as-is.</p>

<p>Rather than creating a Markdown editor from scratch to solve this, several packages are capable of handling the raw Markdown for us. My personal favorite is
<a href="https://github.com/uiwjs/react-md-editor"><code>react-md-editor</code></a>.</p>

<p>Let’s install the package:</p>

<pre><code class="language-bash">npm i @uiw/react-md-editor
</code></pre>

<p>The <code>MDEditor</code> component can be imported and set up as a <a href="https://maxschmitt.me/posts/react-components-controlled-uncontrolled">controlled component</a>:</p>

<pre><code class="language-javascript">import &#42; as React from "react";
import { useState } from "react";
import MDEditor from "@uiw/react-md-editor";

const ReactMDEditor = () =&gt; {
  const [value, setValue] = useState("&#42;&#42;Hello world!!!&#42;&#42;");

  return &lt;MDEditor value={ value } onChange={ setValue } /&gt;;
};
</code></pre>

<p>The plugin also comes with a built-in <code>MDEditor.Markdown</code> component used to preview the rendered content:</p>

<pre><code class="language-javascript">import &#42; as React from "react";
import { useState } from "react";
import MDEditor from "@uiw/react-md-editor";

const ReactMDEditor = () =&gt; {
  const [value, setValue] = useState("&#42;&#42;Hello world!&#42;&#42;");

  return (
    &lt;&gt;
      &lt;MDEditor value={value} onChange={ setValue } /&gt;
      &lt;MDEditor.Markdown source={ value } /&gt;
    &lt;/&gt;
  );
};
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="432"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png"
			
			sizes="100vw"
			alt="Markdown previewer"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The plugin includes a feature that shows a preview of the rendered Markdown. (<a href='https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That was a look at various headaches you might encounter when working with Markdown files in Gatsby. Next, we are turning our attention to another type of file, PDF.</p>

<div class="partners__lead-place"></div>

<h2 id="solving-pdf-headaches-in-gatsby">Solving PDF Headaches In Gatsby</h2>

<p>PDF files handle content with a completely different approach to Markdown files. With Markdown, we simplify the content to its most raw form so it can be easily handled across different front ends. PDFs, however, are the content presented to users on the front end. Rather than extracting the raw content from the file, we want the user to see it as it is, often by making it available for download or embedding it in a way that the user views the contents directly on the page, sort of like a video.</p>

<p>I want to show you four approaches to consider when embedding a PDF file on a page in a Gatsby project.</p>

<h3 id="using-the-iframe-element">Using The <code>&lt;iframe&gt;</code> Element</h3>

<p>The easiest way to embed a PDF into your Gatsby project is perhaps through an <code>iframe</code> element:</p>

<pre><code class="language-javascript">import &#42; as React from "react";
import samplePDF from "./assets/lorem-ipsum.pdf";

const IframePDF = () =&gt; {
  return &lt;iframe src={ samplePDF }&gt;&lt;/iframe&gt;;
};
</code></pre>

<p>It’s worth calling out here that the <code>iframe</code> element supports lazy loading (<code>loading=&quot;lazy&quot;</code>) to boost performance in instances where it doesn’t need to load right away.</p>

<h3 id="embedding-a-third-party-viewer">Embedding A Third-Party Viewer</h3>

<p>There are situations where PDFs are more manageable when stored in a third-party service, such as Drive, which includes a PDF viewer that can embedded directly on the page. In these cases, we can use the same <code>iframe</code> we used above, but with the source pointed at the service.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";

const ThirdPartyIframePDF = () =&gt; {
  return (
    &lt;iframe
      src="https://drive.google.com/file/d/1IiRZOGib&#95;0cZQY9RWEDslMksRykEnrmC/preview"
      allowFullScreen
      title="PDF Sample in Drive"
    /&gt;
  );
};
</code></pre>
</div>

<p>It’s a good reminder that you want to trust the third-party content that’s served in an <code>iframe</code>. If we’re effectively loading a document from someone else’s source that we do not control, your site could become prone to security vulnerabilities should that source become compromised.</p>

<h3 id="using-react-pdf">Using <code>react-pdf</code></h3>

<p>The <code>react-pdf</code> package provides an interface to render PDFs as React components. It is based on <a href="https://github.com/mozilla/pdf.js/"><code>pdf.js</code></a>, a JavaScript library that renders PDFs using <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas">HTML Canvas</a>.</p>

<p>To display a PDF file on a <code>&lt;canvas&gt;</code>, the <code>react-pdf</code> library exposes the <code>Document</code> and <code>Page</code> components:</p>

<ul>
<li><strong><code>Document</code></strong>: Loads the PDF passed in its <code>file</code> prop.</li>
<li><strong><code>Page</code></strong>: Displays the page passed in its <code>pageNumber</code> prop. It should be placed inside <code>Document</code>.</li>
</ul>

<p>We can install to our project:</p>

<pre><code class="language-bash">npm i react-pdf
</code></pre>

<p>Before we put <code>react-pdf</code> to use, we will need to set up a service worker for <code>pdf.js</code> to process time-consuming tasks such as parsing and rendering a PDF document.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { pdfjs } from "react-pdf";

pdfjs.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@3.6.172/build/pdf.worker.min.js";

const ReactPDF = () =&gt; {
  return &lt;div&gt;&lt;/div&gt;;
};
</code></pre>
</div>

<p>Now, we can import the <code>Document</code> and <code>Page</code> components, passing the PDF file to their props. We can also import the component’s necessary styles while we are at it.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { Document, Page } from "react-pdf";

import { pdfjs } from "react-pdf";
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import "react-pdf/dist/esm/Page/TextLayer.css";

import samplePDF from "./assets/lorem-ipsum.pdf";

pdfjs.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@3.6.172/build/pdf.worker.min.js";

const ReactPDF = () =&gt; {
  return (
    &lt;Document file={ samplePDF }&gt;
      &lt;Page pageNumber={ 1 } /&gt;
    &lt;/Document&gt;
  );
};
</code></pre>
</div>

<p>Since accessing the PDF will change the current page, we can add state management by passing the current <code>pageNumber</code> to the <code>Page</code> component:</p>

<pre><code class="language-javascript">import { useState } from "react";

// ...

const ReactPDF = () =&gt; {
  const [currentPage, setCurrentPage] = useState(1);

  return (
    &lt;Document file={ samplePDF }&gt;
      &lt;Page pageNumber={ currentPage } /&gt;
    &lt;/Document&gt;
  );
};
</code></pre>

<p>One issue is that we have pagination but don’t have a way to navigate between pages. We can change that by adding controls. First, we will need to know the number of pages in the document, which is accessed on the <code>Document</code> component’s <code>onLoadSuccess</code> event:</p>

<div class="break-out">
<pre><code class="language-javascript">// ...

const ReactPDF = () =&gt; {
  const [pageNumber, setPageNumber] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);

  const handleLoadSuccess = ({ numPages }) =&gt; {
    setPageNumber(numPages);
  };

  return (
    &lt;Document file={ samplePDF } onLoadSuccess={ handleLoadSuccess }&gt;
      &lt;Page pageNumber={ currentPage } /&gt;
    &lt;/Document&gt;
  );
};
</code></pre>
</div>

<p>Next, we display the current page number and add “Next” and “Previous” buttons with their respective handlers to change the current page:</p>

<div class="break-out">
<pre><code class="language-javascript">// ...

const ReactPDF = () =&gt; {
  const [currentPage, setCurrentPage] = useState(1);
  const [pageNumber, setPageNumber] = useState(null);

  const handlePrevious = () =&gt; {
    // checks if it isn't the first page
    if (currentPage &gt; 1) {
      setCurrentPage(currentPage - 1);
    }
  };

  const handleNext = () =&gt; {
    // checks if it isn't the last page
    if (currentPage &lt; pageNumber) {
      setCurrentPage(currentPage + 1);
    }
  };

  const handleLoadSuccess = ({ numPages }) =&gt; {
    setPageNumber(numPages);
  };

  return (
    &lt;div&gt;
      &lt;Document file={ samplePDF } onLoadSuccess={ handleLoadSuccess }&gt;
        &lt;Page pageNumber={ currentPage } /&gt;
      &lt;/Document&gt;
      &lt;button onClick={ handlePrevious }&gt;Previous&lt;/button&gt;
      &lt;p&gt;{currentPage}&lt;/p&gt;
      &lt;button onClick={ handleNext }&gt;Next&lt;/button&gt;
    &lt;/div&gt;
  );
};
</code></pre>
</div>

<p>This provides us with everything we need to embed a PDF file on a page via the HTML <code>&lt;canvas&gt;</code> element using <code>react-pdf</code> and <code>pdf.js</code>.</p>

<p>There is another similar package capable of embedding a PDF file in a viewer, complete with pagination controls. We’ll look at that next.</p>

<h3 id="using-react-pdf-viewer">Using <code>react-pdf-viewer</code></h3>

<p>Unlike <code>react-pdf</code>, the <code>react-pdf-viewer</code> package provides built-in customizable controls right out of the box, which makes embedding a multi-page PDF file a lot easier than having to import them separately.</p>

<p>Let’s install it:</p>

<div class="break-out">
<pre><code class="language-bash">npm i @react-pdf-viewer/core@3.12.0 @react-pdf-viewer/default-layout
</code></pre>
</div>

<p>Since <code>react-pdf-viewer</code> also relies on <code>pdf.js</code>, we will need to create a service worker as we did with <code>react-pdf</code>, but only if we are not using both packages at the same time. This time, we are using a <code>Worker</code> component with a <code>workerUrl</code> prop directed at the worker’s package.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { Worker } from "@react-pdf-viewer/core";

const ReactPDFViewer = () =&gt; {
  return (
    &lt;&gt;
      &lt;Worker workerUrl="https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js"&gt;&lt;/Worker&gt;
    &lt;/&gt;
  );
};
</code></pre>
</div>

<p>Note that a <strong>worker like this ought to be set just once at the layout level</strong>. This is especially true if you intend to use the PDF viewer across different pages.</p>

<p>Next, we import the <code>Viewer</code> component with its styles and point it at the PDF through its <code>fileUrl</code> prop.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { Viewer, Worker } from "@react-pdf-viewer/core";

import "@react-pdf-viewer/core/lib/styles/index.css";

import samplePDF from "./assets/lorem-ipsum.pdf";

const ReactPDFViewer = () =&gt; {
  return (
    &lt;&gt;
      &lt;Viewer fileUrl={ samplePDF } /&gt;
      &lt;Worker workerUrl="https://unpkg.com/pdfjs-dist@3.6.172/build/pdf.worker.min.js"&gt;&lt;/Worker&gt;
    &lt;/&gt;
  );
};
</code></pre>
</div>

<p>Once again, we need to add controls. We can do that by importing the <code>defaultLayoutPlugin</code> (including its corresponding styles), making an instance of it, and passing it in the <code>Viewer</code> component’s <code>plugins</code> prop.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { Viewer, Worker } from "@react-pdf-viewer/core";
import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout";

import "@react-pdf-viewer/core/lib/styles/index.css";
import "@react-pdf-viewer/default-layout/lib/styles/index.css";

import samplePDF from "./assets/lorem-ipsum.pdf";

const ReactPDFViewer = () =&gt; {
  const defaultLayoutPluginInstance = defaultLayoutPlugin();

  return (
    &lt;&gt;
      &lt;Viewer fileUrl={ samplePDF } plugins={ [defaultLayoutPluginInstance] } /&gt;
      &lt;Worker workerUrl="https://unpkg.com/pdfjs-dist@3.6.172/build/pdf.worker.min.js"&gt;&lt;/Worker&gt;
    &lt;/&gt;
  );
};
</code></pre>
</div>

<p>Again, <code>react-pdf-viewer</code> is an alternative to <code>react-pdf</code> that can be a little easier to implement if you don’t need full control over your PDF files, just the embedded viewer.</p>

<p>There is one more plugin that provides an embedded viewer for PDF files. We will look at it, but only briefly, because I personally do not recommend using it in favor of the other approaches we’ve covered.</p>

<h3 id="why-you-shouldn-t-use-react-file-viewer">Why You Shouldn’t Use <code>react-file-viewer</code></h3>

<p>The last plugin we will check out is <code>react-file-viewer</code>, a package that offers an embedded viewer with a simple interface but with the capacity to handle a variety of media in addition to PDF files, including images, videos, PDFs, documents, and spreadsheets.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import FileViewer from "react-file-viewer";

const PDFReactFileViewer = () =&gt; {
  return &lt;FileViewer fileType="pdf" filePath="/lorem-ipsum.pdf" /&gt;;
};
</code></pre>
</div>

<p>While <code>react-file-viewer</code> will get the job done, <strong>it is extremely outdated</strong> and could easily create more headaches than it solves with compatibility issues. I suggest avoiding it in favor of either an <code>iframe</code>, <code>react-pdf</code>, or <code>react-pdf-viewer</code>.</p>

<div class="partners__lead-place"></div>

<h2 id="solving-3d-model-headaches-in-gatsby">Solving 3D Model Headaches In Gatsby</h2>

<p>I want to cap this brief two-part series with one more media type that might cause headaches in a Gatsby project: <strong>3D models</strong>.</p>

<p>A 3D model file is a digital representation of a three-dimensional object that stores information about the object’s geometry, texture, shading, and other properties of the object. On the web, 3D model files are used to enhance user experiences by bringing interactive and immersive content to websites. You are most likely to encounter them in product visualizations, architectural walkthroughs, or educational simulations.</p>

<p>There is a multitude of 3D model formats, including glTF OBJ, FBX, STL, and so on. We will use glTF models for a demonstration of a headache-free 3D model implementation in Gatsby.</p>

<p>The <a href="https://github.com/KhronosGroup/glTF"><strong>GL Transmission Format</strong></a> <strong>(glTF)</strong> was designed specifically for the web and real-time applications, making it ideal for our example. Using glTF files does require a specific webpack loader, so for simplicity’s sake, we will save the glTF model in the <code>/static</code> folder at the root of our project as we look at two approaches to create the 3D visual with Three.js:</p>

<ol>
<li>Using a vanilla implementation of Three.js,</li>
<li>Using a package that integrates Three.js as a React component.</li>
</ol>

<h3 id="using-three-js">Using Three.js</h3>

<p><a href="https://threejs.org/">Three.js</a> creates and loads interactive 3D graphics directly on the web with the help of <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API">WebGL</a>, a JavaScript API for rendering 3D graphics in real-time inside HTML <code>&lt;canvas&gt;</code> elements.</p>

<p>Three.js is not integrated with React or Gatsby out of the box, so we must modify our code to support it. A Three.js tutorial is out of scope for what we are discussing in this article, although excellent learning resources are available in the <a href="https://threejs.org/docs/">Three.js documentation</a>.</p>

<p>We start by installing the <code>three</code> library to the Gatsby project:</p>

<pre><code class="language-bash">npm i three
</code></pre>

<p>Next, we write a function to load the glTF model for Three.js to reference it. This means we need to import a <code>GLTFLoader</code> add-on to instantiate a new <code>loader</code> object.</p>

<pre><code class="language-javascript">import &#42; as React from "react";
import &#42; as THREE from "three";

import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

const loadModel = async (scene) =&gt; {
  const loader = new GLTFLoader();
};
</code></pre>

<p>We use the <code>scene</code> object as a parameter in the <code>loadModel</code> function so we can attach our 3D model once loaded to the scene.</p>

<p>From here, we use <code>loader.load()</code> which takes four arguments:</p>

<ol>
<li>The glTF file location,</li>
<li>A callback when the resource is loaded,</li>
<li>A callback while loading is in progress,</li>
<li>A callback for handling errors.</li>
</ol>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import &#42; as THREE from "three";

import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

const loadModel = async (scene) =&gt; {
  const loader = new GLTFLoader();

  await loader.load(
    "/strawberry.gltf", // glTF file location
    function (gltf) {
      // called when the resource is loaded
      scene.add(gltf.scene);
    },
    undefined, // called while loading is in progress, but we are not using it
    function (error) {
      // called when loading returns errors
      console.error(error);
    }
  );
};
</code></pre>
</div>

<p>Let’s create a component to host the scene and load the 3D model. We need to know the element’s client <code>width</code> and <code>height</code>, which we can get using React’s <code>useRef</code> hook to access the element’s DOM properties.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import &#42; as THREE from "three";

import { useRef, useEffect } from "react";

// ...

const ThreeLoader = () =&gt; {
  const viewerRef = useRef(null);

  return &lt;div style={ { height: 600, width: "100%" } } ref={ viewerRef }&gt;&lt;/div&gt;; // Gives the element its dimensions
};
</code></pre>
</div>

<p>Since we are using the element’s <code>clientWidth</code> and <code>clientHeight</code> properties, we need to create the scene on the client side inside React’s <code>useEffect</code> hook where we configure the Three.js <code>scene</code> with its necessary complements, e.g., a camera, the WebGL renderer, and lights.</p>

<div class="break-out">
<pre><code class="language-javascript">useEffect(() =&gt; {
  const { current: viewer } = viewerRef;

  const scene = new THREE.Scene();

  const camera = new THREE.PerspectiveCamera(75, viewer.clientWidth / viewer.clientHeight, 0.1, 1000);

  const renderer = new THREE.WebGLRenderer();

  renderer.setSize(viewer.clientWidth, viewer.clientHeight);

  const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(0, 0, 5);
  scene.add(directionalLight);

  viewer.appendChild(renderer.domElement);
  renderer.render(scene, camera);
}, []);
</code></pre>
</div>

<p>Now we can invoke the <code>loadModel</code> function, passing the scene to it as the only argument:</p>

<div class="break-out">
<pre><code class="language-javascript">useEffect(() =&gt; {
  const { current: viewer } = viewerRef;

  const scene = new THREE.Scene();

  const camera = new THREE.PerspectiveCamera(75, viewer.clientWidth / viewer.clientHeight, 0.1, 1000);

  const renderer = new THREE.WebGLRenderer();

  renderer.setSize(viewer.clientWidth, viewer.clientHeight);

  const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(0, 0, 5);
  scene.add(directionalLight);

  loadModel(scene); // Here!

  viewer.appendChild(renderer.domElement);
  renderer.render(scene, camera);
}, []);
</code></pre>
</div>

<p>The last part of this vanilla Three.js implementation is to add <code>OrbitControls</code> that allow users to navigate the model. That might look something like this:</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import &#42; as THREE from "three";

import { useRef, useEffect } from "react";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

const loadModel = async (scene) =&gt; {
  const loader = new GLTFLoader();

  await loader.load(
    "/strawberry.gltf", // glTF file location
    function (gltf) {
      // called when the resource is loaded
      scene.add(gltf.scene);
    },
    undefined, // called while loading is in progress, but it is not used
    function (error) {
      // called when loading has errors
      console.error(error);
    }
  );
};

const ThreeLoader = () =&gt; {
  const viewerRef = useRef(null);

  useEffect(() =&gt; {
    const { current: viewer } = viewerRef;

    const scene = new THREE.Scene();

    const camera = new THREE.PerspectiveCamera(75, viewer.clientWidth / viewer.clientHeight, 0.1, 1000);

    const renderer = new THREE.WebGLRenderer();

    renderer.setSize(viewer.clientWidth, viewer.clientHeight);

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff);
    directionalLight.position.set(0, 0, 5);
    scene.add(directionalLight);

    loadModel(scene);

    const target = new THREE.Vector3(-0.5, 1.2, 0);
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.target = target;

    viewer.appendChild(renderer.domElement);

    var animate = function () {
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    };
    animate();
  }, []);

  &lt;div style={ { height: 600, width: "100%" } } ref={ viewerRef }&gt;&lt;/div&gt;;
};
</code></pre>
</div>

<p>That is a straight Three.js implementation in a Gatsby project. Next is another approach using a library.</p>

<h3 id="using-react-three-fiber">Using React Three Fiber</h3>

<p><code>react-three-fiber</code> is a library that integrates the Three.js with React. One of its advantages over the vanilla Three.js approach is its ability to manage and update 3D scenes, making it easier to compose scenes without manually handling intricate aspects of Three.js.</p>

<p>We begin by installing the library to the Gatsby project:</p>

<pre><code class="language-bash">npm i react-three-fiber @react-three/drei
</code></pre>

<p>Notice that the installation command includes the <code>@react-three/drei</code> package, which we will use to add controls to the 3D viewer.</p>

<p>I personally love <code>react-three-fiber</code> for being tremendously self-explanatory. For example, I had a relatively easy time migrating the extensive chunk of code from the vanilla approach to this much cleaner code:</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";
import { useLoader, Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

const ThreeFiberLoader = () =&gt; {
  const gltf = useLoader(GLTFLoader, "/strawberry.gltf");

  return (
    &lt;Canvas camera={ { fov: 75, near: 0.1, far: 1000, position: [5, 5, 5] } } style={ { height: 600, width: "100%" } }&gt;
      &lt;ambientLight intensity={ 0.4 } /&gt;
      &lt;directionalLight color="white" /&gt;
      &lt;primitive object={ gltf.scene } /&gt;
      &lt;OrbitControls makeDefault /&gt;
    &lt;/Canvas&gt;
  );
};
</code></pre>
</div>

<p>Thanks to <code>react-three-fiber</code>, we get the same result as a vanilla Three.js implementation but with fewer steps, more efficient code, and a slew of abstractions for managing and updating Three.js scenes.</p>

<h2 id="two-final-tips">Two Final Tips</h2>

<p>The last thing I want to leave you with is two final considerations to take into account when working with media files in a Gatsby project.</p>

<h3 id="bundling-assets-via-webpack-and-the-static-folder">Bundling Assets Via Webpack And The <code>/static</code> Folder</h3>

<p>Importing an asset as a module so it can be bundled by webpack is a common strategy to add post-processing and minification, as well as hashing paths on the client. But there are two additional use cases where you might want to avoid it altogether and use the <code>static</code> folder in a Gatsby project:</p>

<ul>
<li>Referencing a library outside the bundled code to prevent webpack compatibility issues or a lack of specific loaders.</li>
<li>Referencing assets with a specific name, for example, in a web manifest file.</li>
</ul>

<p>You can find a <a href="https://www.gatsbyjs.com/docs/how-to/images-and-media/static-folder/">detailed explanation of the <code>static</code> folder and use it to your advantage</a> in the Gatsby documentation.</p>

<h3 id="embedding-files-from-third-party-services">Embedding Files From Third-Party Services</h3>

<p>Secondly, you can never be too cautious when embedding third-party services on a website. Replaced content elements, like <code>&lt;iframe&gt;</code>, can introduce various security vulnerabilities, particularly when you do not have control of the source content. By integrating a third party’s scripts, widgets, or content, a website or app is prone to potential vulnerabilities, such as iframe injection or cross-frame scripting.</p>

<p>Moreover, if an integrated third-party service experiences downtime or performance issues, it can directly impact the user experience.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This article explored various approaches for working around common headaches you may encounter when working with Markdown, PDF, and 3D model files in a Gatsby project. In the process, we leveraged several React plugins and Gatsby features that handle how content is parsed, embed files on a page, and manage 3D scenes.</p>

<p>This is also the second article in a brief two-part series that addresses common headaches working with a variety of media types in Gatsby. The <a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/">first part covers more common media files</a>, including images, video, and audio.</p>

<p>If you’re looking for more cures to Gatsby headaches, please <a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/">check out my other two-part series</a> that investigates internationalization.</p>

<h2 id="see-also">See Also</h2>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/">Gatsby Headaches And How To Cure Them: i18n (Part 1)</a>”</li>
<li>“<a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-2/">Gatsby Headaches And How To Cure Them: i18n (Part 2)</a>”</li>
<li>“<a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/">Gatsby Headaches And How To Cure Them: Media Files (Part 1)</a>”</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Juan Diego Rodríguez</author><title>Gatsby Headaches: Working With Media (Part 1)</title><link>https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/</link><pubDate>Mon, 09 Oct 2023 11:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/</guid><description>Gatsby is an exceptionally flexible static site generator with a robust community and ecosystem to extend a Gatsby site with additional features. Did you know that there are more than a dozen image-related plugins alone? Juan Rodriguez shares what he’s learned about optimizing files for improved performance after working with different plugins and techniques.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/" />
              <title>Gatsby Headaches: Working With Media (Part 1)</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Gatsby Headaches: Working With Media (Part 1)</h1>
                  
                    
                    <address>Juan Diego Rodríguez</address>
                  
                  <time datetime="2023-10-09T11:00:00&#43;00:00" class="op-published">2023-10-09T11:00:00+00:00</time>
                  <time datetime="2023-10-09T11:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Working with media files in Gatsby might not be as straightforward as expected. I remember starting my first Gatsby project. After consulting Gatsby’s documentation, I discovered I needed to use the <code>gatsby-source-filesystem</code> plugin to make queries for local files. Easy enough!</p>

<p>That’s where things started getting complicated. Need to use images? Check the docs and install one &mdash; or more! &mdash; of the many, many plugins available for handling images. How about working with SVG files? There is another plugin for that. Video files? You get the idea.</p>

<p>It’s all great until any of those plugins or packages become outdated and go unmaintained. That’s where the headaches start.</p>

<p>If you are unfamiliar with <a href="https://www.gatsbyjs.com">Gatsby</a>, it’s a React-based static site generator that uses GraphQL to pull structured data from various sources and uses webpack to bundle a project so it can then be deployed and served as static files. It’s essentially a static site generator with reactivity that can pull data from a vast array of sources.</p>

<p>Like many static site frameworks in the Jamstack, Gatsby has traditionally enjoyed a great reputation as a <a href="https://www.gatsbyjs.com/blog/comparing-website-performance-gatsby-vs-next-vs-nuxt/">performant framework</a>, although <a href="https://2022.stateofjs.com/en-US/libraries/">it has taken a hit in recent years</a>. Based on what I’ve seen, however, it’s not so much that the framework is fast or slow but how the framework is configured to handle many of the sorts of things that impact performance, including media files.</p>

<p>So, let’s solve the headaches you might encounter when working with media files in a Gatsby project. This article is the first of a brief two-part series where we will look specifically at the media you are most likely to use: images, video, and audio. After that, the <a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/">second part of this series</a> will get into different types of files, including Markdown, PDFs, and even 3D models.</p>

<h2 id="solving-image-headaches-in-gatsby">Solving Image Headaches In Gatsby</h2>

<p>I think that the process of optimizing images can fall into four different buckets:</p>

<ol>
<li><strong>Optimize image files.</strong><br />
Minimizing an image’s file size without losing quality directly leads to shorter fetching times. This can be done manually or during a build process. It’s also possible to use a service, like Cloudinary, to handle the work on demand.</li>
<li><strong>Prioritize images that are part of the First Contentful Paint (FCP).</strong><br />
FCP is a <a href="https://developer.mozilla.org/en-US/docs/Glossary/First_contentful_paint">metric that measures the time</a> between the point when a page starts loading to when the first bytes of content are rendered. The idea is that fetching assets that are part of that initial render earlier results in faster loading rather than waiting for other assets lower on the chain.</li>
<li><strong>Lazy loading other images.</strong><br />
We can prevent the rest of the images from render-blocking other assets using the <code>loading=&quot;lazy&quot;</code> attribute on images.</li>
<li><strong>Load the right image file for the right context.</strong><br />
With <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">responsive images</a>, we can serve one version of an image file at one screen size and serve another image at a different screen size with the <code>srcset</code> and <code>sizes</code> attributes or with the <code>&lt;picture&gt;</code> element.</li>
</ol>

<p>These are great principles for any website, not only those built with Gatsby. But how we build them into a Gatsby-powered site can be confusing, which is why I’m writing this article and perhaps why you’re reading it.</p>

<h3 id="lazy-loading-images-in-gatsby">Lazy Loading Images In Gatsby</h3>

<p>We can apply an image to a React component in a Gatsby site like this:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import forest from "./assets/images/forest.jpg";

const ImageHTML = () =&gt; {
  return &lt;img src={ forest } alt="Forest trail" /&gt;;
};
</code></pre>

<p>It’s important to <code>import</code> the image as a JavaScript module. This lets webpack know to bundle the image and generate a path to its location in the public folder.</p>

<p>This works fine, but when are we ever working with only one image? What if we want to make an image gallery that contains 100 images? If we try to load that many <code>&lt;img&gt;</code> tags at once, they will certainly slow things down and could affect the FCP. That’s where the third principle that uses the <code>loading=&quot;lazy&quot;</code> attribute can come into play.</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import forest from "./assets/images/forest.jpg";

const LazyImageHTML = () =&gt; {
  return &lt;img src={ forest } loading="lazy" alt="Forest trail" /&gt;;
};
</code></pre>

<p>We can do the opposite with <code>loading=&quot;eager&quot;</code>. It instructs the browser to load the image as soon as possible, regardless of whether it is onscreen or not.</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import forest from "./assets/images/forest.jpg";

const EagerImageHTML = () =&gt; {
  return &lt;img src={ forest } loading="eager" alt="Forest trail" /&gt;;
};
</code></pre>

<h3 id="implementing-responsive-images-in-gatsby">Implementing Responsive Images In Gatsby</h3>

<p>This is a basic example of the HTML for responsive images:</p>

<div class="break-out">
<pre><code class="language-html">&lt;img
  srcset="./assets/images/forest-400.jpg 400w, ./assets/images/forest-800.jpg 800w"
  sizes="(max-width: 500px) 400px, 800px"
  alt="Forest trail"
/&gt;
</code></pre>
</div>

<p>In Gatsby, we must <code>import</code> the images first and pass them to the <code>srcset</code> attribute as template literals so webpack can bundle them:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import forest800 from "./assets/images/forest-800.jpg";

import forest400 from "./assets/images/forest-400.jpg";

const ResponsiveImageHTML = () =&gt; {
  return (
    &lt;img
      srcSet={`

        ${ forest400 } 400w,

        ${ forest800 } 800w

      `}
      sizes="(max-width: 500px) 400px, 800px"
      alt="Forest trail"
    /&gt;
  );
};
</code></pre>

<p>That should take care of any responsive image headaches in the future.</p>

<h3 id="loading-background-images-in-gatsby">Loading Background Images In Gatsby</h3>

<p>What about pulling in the URL for an image file to use on the CSS <code>background-url</code> property? That looks something like this:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import "./style.css";

const ImageBackground = () =&gt; {
  return &lt;div className="banner"&gt;&lt;/div&gt;;
};
</code></pre>

<pre><code class="language-css">/&#42; style.css &#42;/

.banner {
    aspect-ratio: 16/9;
      background-size: cover;

    background-image: url("./assets/images/forest-800.jpg");

  /&#42; etc. &#42;/
}
</code></pre>

<p>This is straightforward, but there is still room for optimization! For example, we can do the CSS version of responsive images, which loads the version we want at specific breakpoints.</p>

<pre><code class="language-css">/&#42; style.css &#42;/

@media (max-width: 500px) {
  .banner {
    background-image: url("./assets/images/forest-400.jpg");
  }
}
</code></pre>

<h3 id="using-the-gatsby-source-filesystem-plugin">Using The <code>gatsby-source-filesystem</code> Plugin</h3>

<p>Before going any further, I think it is worth installing the <code>gatsby-source-filesystem</code> plugin. It’s an essential part of any Gatsby project because it allows us to <a href="https://www.gatsbyjs.com/docs/how-to/sourcing-data/sourcing-from-the-filesystem/">query data from various directories in the local filesystem</a>, making it simpler to fetch assets, like a folder of optimized images.</p>

<pre><code class="language-bash">npm i gatsby-source-filesystem
</code></pre>

<p>We can add it to our <code>gatsby-config.js</code> file and specify the directory from which we will query our media assets:</p>

<pre><code class="language-javascript">// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,

      options: {
        name: `assets`,

        path: `${ &#95;&#95;dirname }/src/assets`,
      },
    },
  ],
};
</code></pre>

<p>Remember to restart your development server to see changes from the <code>gatsby-config.js</code> file.</p>

<p>Now that we have <code>gatsby-source-filesystem</code> installed, we can continue solving a few other image-related headaches. For example, the next plugin we look at is capable of simplifying the cures we used for lazy loading and responsive images.</p>

<h3 id="using-the-gatsby-plugin-image-plugin">Using The <code>gatsby-plugin-image</code> Plugin</h3>

<p>The <a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-image/"><code>gatsby-plugin-image</code> plugin</a> (not to be confused with the outdated <a href="https://www.gatsbyjs.com/plugins/gatsby-image/"><code>gatsby-image</code> plugin</a>) uses techniques that <strong>automatically handle various aspects of image optimization</strong>, such as lazy loading, responsive sizing, and even generating optimized image formats for modern browsers.</p>

<p>Once installed, we can replace standard <code>&lt;img&gt;</code> tags with either the <code>&lt;GatsbyImage&gt;</code> or <code>&lt;StaticImage&gt;</code> components, depending on the use case. These components take advantage of the plugin’s features and use the <code>&lt;picture&gt;</code> HTML element to ensure the most appropriate image is served to each user based on their device and network conditions.</p>

<p>We can start by installing <code>gatsby-plugin-image</code> and the other plugins it depends on:</p>

<div class="break-out">
<pre><code class="language-bash">npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp
</code></pre>
</div>

<p>Let’s add them to the <code>gatsby-config.js</code> file:</p>

<pre><code class="language-javascript">// gatsby-config.js

module.exports = {
plugins: [

// other plugins
`gatsby-plugin-image`,
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`],

};
</code></pre>

<p>This provides us with some features we will put to use a bit later.</p>

<h3 id="using-the-staticimage-component">Using The <code>StaticImage</code> Component</h3>

<p>The <code>StaticImage</code> component serves <strong>images that don’t require dynamic sourcing or complex transformations</strong>. It’s particularly useful for scenarios where you have a fixed image source that doesn’t change based on user interactions or content updates, like logos, icons, or other static images that remain consistent.</p>

<p>The main attributes we will take into consideration are:</p>

<ul>
<li><strong><code>src</code>:</strong> This attribute is required and should be set to the path of the image you want to display.</li>
<li><strong><code>alt</code>:</strong> Provides alternative text for the image.</li>
<li><strong><code>placeholder</code>:</strong> This attribute can be set to either <code>blurred</code> or <code>dominantColor</code> to define the type of placeholder to display while the image is loading.</li>
<li><strong><code>layout</code>:</strong> This defines how the image should be displayed. It can be set to <code>fixed</code> for, as you might imagine, images with a fixed size, <code>fullWidth</code> for images that span the entire container, and <code>constrained</code> for images scaled down to fit their container.</li>
<li><strong><code>loading</code>:</strong> This determines when the image should start loading while also supporting the <code>eager</code> and <code>lazy</code> options.</li>
</ul>

<p>Using <code>StaticImage</code> is similar to using a regular HTML <code>&lt;img&gt;</code> tag. However, <code>StaticImage</code> requires passing the string directly to the <code>src</code> attribute so it can be bundled by webpack.</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import { StaticImage } from "gatsby-plugin-image";

const ImageStaticGatsby = () =&gt; {
  return (
    &lt;StaticImage
      src="./assets/images/forest.jpg"
      placeholder="blurred"
      layout="constrained"
      alt="Forest trail"
      loading="lazy"
    /&gt;
  );
  };
</code></pre>

<p>The <code>StaticImage</code> component is great, but you have to take its constraints into account:</p>

<ul>
<li><strong>No Dynamically Loading URLs</strong><br />
One of the most significant limitations is that the <code>StaticImage</code> component doesn’t support dynamically loading images based on URLs fetched from data sources or APIs.</li>
<li><strong>Compile-Time Image Handling</strong><br />
The <code>StaticImage</code> component’s image handling occurs at compile time. This means that the images you specify are processed and optimized when the Gatsby site is built. Consequently, if you have images that need to change frequently based on user interactions or updates, the static nature of this component might not fit your needs.</li>
<li><strong>Limited Transformation Options</strong><br />
Unlike the more versatile <code>GatsbyImage</code> component, the <code>StaticImage</code> component provides fewer transformation options, e.g., there is no way to apply complex transformations like cropping, resizing, or adjusting image quality directly within the component. You may want to consider alternative solutions if you require advanced transformations.</li>
</ul>

<h3 id="using-the-gatsbyimage-component">Using The <code>GatsbyImage</code> Component</h3>

<p>The <code>GatsbyImage</code> component is a <strong>more versatile solution</strong> that addresses the limitations of the <code>StaticImage</code> component. It’s particularly useful for scenarios involving dynamic image loading, complex transformations, and advanced customization.</p>

<p>Some ideal use cases where <code>GatsbyImage</code> is particularly useful include:</p>

<ul>
<li><strong>Dynamic Image Loading</strong><br />
If you need to load images dynamically based on data from APIs, content management systems, or other sources, the <code>GatsbyImage</code> component is the go-to choice. It can fetch images and optimize their loading behavior.</li>
<li><strong>Complex transformations</strong><br />
The <code>GatsbyImage</code> component is well-suited for advanced transformations, using GraphQL queries to apply them.</li>
<li><strong>Responsive images</strong><br />
For responsive design, the <code>GatsbyImage</code> component excels by automatically generating multiple sizes and formats of an image, ensuring that users receive an appropriate image based on their device and network conditions.</li>
</ul>

<p>Unlike the <code>StaticImage</code> component, which uses a <code>src</code> attribute, <code>GatsbyImage</code> has an <code>image</code> attribute that takes a <code>gatsbyImageData</code> object. <code>gatsbyImageData</code> contains the image information and can be queried from GraphQL using the following query.</p>

<pre><code class="language-javascript">query {
  file(name: { eq: "forest" }) {
    childImageSharp {
      gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
    }

    name
  }
}
</code></pre>

<p>If you’re following along, you can look around your Gatsby data layer at <code>http://localhost:8000/___graphql</code>.</p>

<p>From here, we can use the <code>useStaticQuery</code> hook and the <code>graphql</code> tag to fetch data from the data layer:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import { useStaticQuery, graphql } from "gatsby";

import { GatsbyImage, getImage } from "gatsby-plugin-image";

const ImageGatsby = () =&gt; {
  // Query data here:

  const data = useStaticQue(graphql``);

  return &lt;div&gt;&lt;/div&gt;;
};
</code></pre>

<p>Next, we can write the GraphQL query inside of the <code>graphql</code> tag:</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";

import { useStaticQuery, graphql } from "gatsby";

const ImageGatsby = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      file(name: { eq: "forest" }) {
        childImageSharp {
          gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
        }

        name
      }
    }
  `);

  return &lt;div&gt;&lt;/div&gt;;
};
</code></pre>
</div>

<p>Next, we import the <code>GatsbyImage</code> component from <code>gatsby-plugin-image</code> and assign the image’s <code>gatsbyImageData</code> property to the <code>image</code> attribute:</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";

import { useStaticQuery, graphql } from "gatsby";

import { GatsbyImage } from "gatsby-plugin-image";

const ImageGatsby = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      file(name: { eq: "forest" }) {
        childImageSharp {
          gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
        }

        name
      }
    }
  `);

  return &lt;GatsbyImage image={ data.file.childImageSharp.gatsbyImageData } alt={ data.file.name } /&gt;;
};
</code></pre>
</div>

<p>Now, we can use the <code>getImage</code> helper function to make the code easier to read. When given a <code>File</code> object, the function returns the <code>file.childImageSharp.gatsbyImageData</code> property, which can be passed directly to the <code>GatsbyImage</code> component.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";

import { useStaticQuery, graphql } from "gatsby";

import { GatsbyImage, getImage } from "gatsby-plugin-image";

const ImageGatsby = () =&gt; {
  const data = useStaticQuery(graphql`
    query {
      file(name: { eq: "forest" }) {
        childImageSharp {
          gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
        }

        name
      }
    }
  `);

  const image = getImage(data.file);

  return &lt;GatsbyImage image={ image } alt={ data.file.name } /&gt;;
};
</code></pre>
</div>

<h3 id="using-the-gatsby-background-image-plugin">Using The <code>gatsby-background-image</code> Plugin</h3>

<p>Another plugin we could use to take advantage of Gatsby’s image optimization capabilities is the <a href="https://www.gatsbyjs.com/plugins/gatsby-background-image/"><code>gatsby-background-image</code> plugin</a>. However, I do not recommend using this plugin since it is outdated and prone to compatibility issues. Instead, <a href="https://www.gatsbyjs.com/plugins/gatsby-background-image/#gatsby-34--gatsby-plugin-image">Gatsby suggests</a> using <code>gatsby-plugin-image</code> when working with the latest Gatsby version 3 and above.</p>

<p>If this compatibility doesn’t represent a significant problem for your project, you can refer to <a href="https://www.gatsbyjs.com/plugins/gatsby-background-image/#table-of-contents">the plugin’s documentation for specific instructions</a> and use it in place of the CSS <code>background-url</code> usage I described earlier.</p>

<h2 id="solving-video-and-audio-headaches-in-gatsby">Solving Video And Audio Headaches In Gatsby</h2>

<p>Working with videos and audio can be a bit of a mess in Gatsby since it lacks plugins for sourcing and optimizing these types of files. In fact, <a href="https://www.gatsbyjs.com/docs/how-to/images-and-media/working-with-video/#hosting-your-own-html5-video-files">Gatsby’s documentation</a> doesn’t name or recommend any official plugins we can turn to.</p>

<p>That means we will have to use <em>vanilla</em> methods for videos and audio in Gatsby.</p>

<h3 id="using-the-html-video-element">Using The HTML <code>video</code> Element</h3>

<p>The HTML <code>video</code> element is capable of serving different versions of the same video using the <code>&lt;source&gt;</code> tag, much like the <code>img</code> element uses the <code>srset</code> attribute to do the same for responsive images.</p>

<p>That allows us to not only serve a more performant video format but also to provide a fallback video for older browsers that may not support the bleeding edge:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import natureMP4 from "./assets/videos/nature.mp4";

import natureWEBM from "./assets/videos/nature.webm";

const VideoHTML = () =&gt; {
  return (
    &lt;video controls&gt;
      &lt;source src={ natureMP4 } type="video/mp4" /&gt;

      &lt;source src={ natureWEBM } type="video/webm" /&gt;
    &lt;/video&gt;
  );
};

P;
</code></pre>

<p>We can also apply lazy loading to videos like we do for images. While videos do not support the <code>loading=&quot;lazy&quot;</code> attribute, there is a <code>preload</code> attribute that is similar in nature. When set to <code>none</code>, the attribute instructs the browser to load a video and its metadata only when the user interacts with it. In other words, it’s lazy-loaded until the user taps or clicks the video.</p>

<p>We can also set the attribute to <code>metadata</code> if we want the video’s details, such as its duration and file size, fetched right away.</p>

<pre><code class="language-javascript">&lt;video controls preload="none"&gt;
  &lt;source src={ natureMP4 } type="video/mp4" /&gt;

  &lt;source src={ natureWEBM } type="video/webm" /&gt;
&lt;/video&gt;
</code></pre>

<p><strong>Note</strong>: <em>I personally do not recommend using the <code>autoplay</code> attribute since it is disruptive and disregards the <code>preload</code> attribute, causing the video to load right away.</em></p>

<p>And, like images, display a placeholder image for a video while it is loading with the <code>poster</code> attribute pointing to an image file.</p>

<pre><code class="language-javascript">&lt;video controls preload="none" poster={ forest }&gt;
  &lt;source src={ natureMP4 } type="video/mp4" /&gt;

  &lt;source src={ natureWEBM } type="video/webm" /&gt;
&lt;/video&gt;
</code></pre>

<h3 id="using-the-html-audio-element">Using The HTML <code>audio</code> Element</h3>

<p>The <code>audio</code> and <code>video</code> elements behave similarly, so adding an <code>audio</code> element in Gatsby looks nearly identical, aside from the element:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import audioSampleMP3 from "./assets/audio/sample.mp3";

import audioSampleWAV from "./assets/audio/sample.wav";

const AudioHTML = () =&gt; {
  return (
    &lt;audio controls&gt;
      &lt;source src={ audioSampleMP3 } type="audio/mp3" /&gt;

      &lt;source src={ audioSampleWAV } type="audio/wav" /&gt;
    &lt;/audio&gt;
  );
};
</code></pre>

<p>As you might expect, the <code>audio</code> element also supports the <code>preload</code> attribute:</p>

<pre><code class="language-javascript">&lt;audio controls preload="none"&gt;
  &lt;source src={ audioSampleMP3 } type="audio/mp3" /&gt;

  &lt;source src={a udioSampleWAV } type="audio/wav" /&gt;
&lt;/audio&gt;
</code></pre>

<p>This is probably as good as we can do to use videos and images in Gatsby with performance in mind, aside from saving and compressing the files as best we can before serving them.</p>

<h2 id="solving-iframe-headaches-in-gatsby">Solving iFrame Headaches In Gatsby</h2>

<p>Speaking of video, what about ones embedded in an <code>&lt;iframe&gt;</code> like we might do with a video from YouTube, Vimeo, or some other third party? Those can certainly lead to performance headaches, but it’s not as we have direct control over the video file and where it is served.</p>

<p>Not all is lost because the HTML <code>iframe</code> element supports lazy loading the same way that images do.</p>

<div class="break-out">
<pre><code class="language-javascript">import &#42; as React from "react";

const VideoIframe = () =&gt; {
  return (
    &lt;iframe
      src="https://www.youtube.com/embed/jNQXAC9IVRw"
      title="Me at the Zoo"
      allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
      allowFullScreen
      loading="lazy"
    /&gt;
  );
};
</code></pre>
</div>

<p>Embedding a third-party video player via <code>iframe</code> can possibly be an easier path than using the HTML <code>video</code> element. <code>iframe</code> elements are cross-platform compatible and could reduce hosting demands if you are working with heavy video files on your own server.</p>

<p>That said, an <code>iframe</code> is essentially a <strong>sandbox serving a page from an outside source</strong>. They’re not weightless, and we have no control over the code they contain. There are also GDPR considerations when it comes to services (such as YouTube) due to cookies, data privacy, and third-party ads.</p>

<h2 id="solving-svg-headaches-in-gatsby">Solving SVG Headaches In Gatsby</h2>

<p>SVGs contribute to improved page performance in several ways. Their vector nature results in a <strong>much smaller file size</strong> compared to raster images, and they <strong>can be scaled up without compromising quality</strong>. And SVGs can be compressed with GZIP, further reducing file sizes.</p>

<p>That said, there are several ways that we can use SVG files. Let’s tackle each one in the contact of Gatsby.</p>

<h3 id="using-inline-svg">Using Inline SVG</h3>

<p>SVGs are essentially lines of code that describe shapes and paths, making them lightweight and highly customizable. Due to their XML-based structure, SVG images can be directly embedded within the HTML <code>&lt;svg&gt;</code> tag.</p>

<pre><code class="language-javascript">import &#42; as React from "react";



const SVGInline = () =&gt; {

  return (

    &lt;svg viewBox="0 0 24 24" fill="#000000"&gt;

      &lt;!-- etc. --&gt;

    &lt;/svg&gt;

  );

};
</code></pre>

<p>Just remember to change certain SVG attributes, such as <code>xmlns:xlink</code> or <code>xlink:href</code>, to JSX attribute spelling, like <code>xmlnsXlink</code> and <code>xlinkHref</code>, respectively.</p>

<h3 id="using-svg-in-img-elements">Using SVG In <code>img</code> Elements</h3>

<p>An SVG file can be passed into an <code>img</code> element&rsquo;s <code>src</code> attribute like any other image file.</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import picture from "./assets/svg/picture.svg";

const SVGinImg = () =&gt; {
  return &lt;img src={ picture } alt="Picture" /&gt;;
};
</code></pre>

<p>Loading SVGs inline or as HTML images are the <em>de facto</em> approaches, but there are React and Gatsby plugins capable of simplifying the process, so let’s look at those next.</p>

<h3 id="inlining-svg-with-the-react-svg-plugin">Inlining SVG With The <code>react-svg</code> Plugin</h3>

<p><a href="https://github.com/tanem/react-svg"><code>react-svg</code></a> provides an efficient way to render SVG images as React components by swapping a <code>ReactSVG</code> component in the DOM with an inline SVG.</p>

<p>Once installing the plugin, import the <code>ReactSVG</code> component and assign the SVG file to the component’s <code>src</code> attribute:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import { ReactSVG } from "react-svg";

import camera from "./assets/svg/camera.svg";

const SVGReact = () =&gt; {
  return &lt;ReactSVG src={ camera } /&gt;;
};
</code></pre>

<h3 id="using-the-gatsby-plugin-react-svg-plugin">Using The <code>gatsby-plugin-react-svg</code> Plugin</h3>

<p>The <a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-react-svg/"><code>gatsby-plugin-react-svg</code> plugin</a> adds <a href="https://github.com/jhamlet/svg-react-loader">svg-react-loader</a> to your Gatsby project’s webpack configuration. The plugin <strong>adds a loader to support using SVG files as React components while bundling them as inline SVG</strong>.</p>

<p>Once the plugin is installed, add it to the <code>gatsby-config.js</code> file. From there, add a webpack rule inside the plugin configuration to only load SVG files ending with a certain filename, making it easy to split inline SVGs from other assets:</p>

<pre><code class="language-javascript">// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: "gatsby-plugin-react-svg",

      options: {
        rule: {
          include: /\.inline\.svg$/,
        },
      },
    },
  ],
};
</code></pre>

<p>Now we can import SVG files like any other React component:</p>

<pre><code class="language-javascript">import &#42; as React from "react";

import Book from "./assets/svg/book.inline.svg";

const GatsbyPluginReactSVG = () =&gt; {
  return &lt;Book /&gt;;
};
</code></pre>

<p>And just like that, we can use SVGs in our Gatsby pages in several different ways!</p>

<h2 id="conclusion">Conclusion</h2>

<p>Even though I personally love Gatsby, working with media files has given me more than a few headaches.</p>

<p>As a final tip, when needing common features such as images or querying from your local filesystem, go ahead and install the necessary plugins. But when you need a minor feature, try doing it yourself with the methods that are already available to you!</p>

<p>If you have experienced different headaches when working with media in Gatsby or have circumvented them with different approaches than what I’ve covered, please share them! This is a big space, and it’s always helpful to see how others approach similar challenges.</p>

<p>Again, this article is the first of a brief two-part series on curing headaches when working with media files in a Gatsby project. The <a href="https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/">following article</a> will be about avoiding headaches when working with different media files, including Markdown, PDFs, and 3D models.</p>

<h3 id="further-reading">Further Reading</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2022/06/demystifying-gatsby4-framework/">Demystifying The New Gatsby Framework</a>”</li>
<li>“<a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/">Gatsby Headaches And How To Cure Them: i18n (Part 1)</a>”</li>
<li>“<a href="https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-2/">Gatsby Headaches And How To Cure Them: i18n (Part 2)</a>”</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Geoff Graham</author><title>Meet Codux: The React Visual Editor That Improves Developer Experience</title><link>https://www.smashingmagazine.com/2023/06/codux-react-visual-editor-improves-developer-experience/</link><pubDate>Thu, 15 Jun 2023 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/06/codux-react-visual-editor-improves-developer-experience/</guid><description>&lt;a href="https://codux.hopp.to/smashing">Codux&lt;/a> is a new visual IDE brought to you by the fine folks over at&lt;a href="https://www.wix.com/"> Wix&lt;/a>. It’s got a lot of low-code features that level the playing field for app development but is really designed for React developers. How so, you ask? That’s what we’re going to explore in this article.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/06/codux-react-visual-editor-improves-developer-experience/" />
              <title>Meet Codux: The React Visual Editor That Improves Developer Experience</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Meet Codux: The React Visual Editor That Improves Developer Experience</h1>
                  
                    
                    <address>Geoff Graham</address>
                  
                  <time datetime="2023-06-15T08:00:00&#43;00:00" class="op-published">2023-06-15T08:00:00+00:00</time>
                  <time datetime="2023-06-15T08:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Wix</b></p>
                

<p>Personally, I get tired of the antics at the start of any new project. I’m a contractor, too, so there’s always some new dependency I need to adopt, config files to force me to write the way a certain team likes, and deployment process I need to plug into. It’s never a fire-up-and-go sort of thing, and it often takes the better part of a working day to get it all right.</p>

<p>There are a lot of moving pieces to a project, right? Everything &mdash; from integrating a framework and establishing a component library to collaboration and deployments &mdash; is a separate but equally important part of your IDE. If you’re like me, jumping between apps and systems is something you get used to. But honestly, it’s an act of Sisyphus rolling the stone up the mountain each time, only to do it again on the next project.</p>

<p>That’s the setup for what I think is a pretty darn good approach to streamline this convoluted process in a way that supports any common project structure and is capable of enhancing it with visual editing capabilities. <a href="https://codux.hopp.to/smashing">It’s called Codux</a>, and if you stick with me for a moment, I think you’ll agree that Codux could be the one-stop shop for everything you need to build production-ready React apps.</p>

<h2 id="codux-is-more-your-code-than-low-code">Codux is More “Your-Code” Than &ldquo;Low-Code&rdquo;</h2>

<p>I know, I know. <em>&ldquo;Yay, another visual editor!&rdquo;</em> says no one, ever. The planet is already full of those, and they’re really designed to give folks developer superpowers without actually doing any development.</p>

<p>That’s so not the case with Codux. There are indeed a lot of &ldquo;low-code&rdquo; affordances that could empower non-developers, but that’s not the headlining feature of Codux or really who or what it caters to. Instead, Codux is a fully-integrated IDE that provides the bones of your project while improving the developer experience instead of abstracting it away.</p>

<p>Do you use CodePen? What makes it so popular (and great to use) is that it &ldquo;just&rdquo; works. It combines frameworks, preprocessors, a live rendering environment, and modern build tools into a single interface that does all the work on &ldquo;Save&rdquo;. But I still get to write code in a single place, the way I like it.</p>

<p>I see Codux a lot like that. But bigger. Not bigger in the sense of more complicated, but bigger in that it is more integrated than frameworks and build tools. It <em>is</em> your framework. It <em>is</em> your component library. It <em>is</em> your build process. And it just so happens to have incredibly powerful visual editing controls that are fully integrated with your code editor.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://codux.hopp.to/smashing">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="524"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png"
			
			sizes="100vw"
			alt="Creating a new project with Codux"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      There’s a growing collection of frameworks and starters right out of the box. (<a href='https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That’s why it makes more sense to call Codux “your code” instead of the typical low-code or no-code visual editing tools. Those are designed for non-developers. Codux, on the other hand, is made <em>for developers</em>.</p>

<p>In fact, here’s a pretty fun thing to do. Open a component file from your project in VS Code and put the editor window next to the Codux window open to the same component. Make a small CSS change or something and watch both the preview rendering <em>and</em> code update instantly in Codux.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/836090503"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>It’s <em>that</em> tightly integrated.</p>

<h2 id="it-s-your-visual-guide-for-react">It’s Your Visual Guide For React</h2>

<p>That’s really the crux of Codux. It really <em>is</em> a visual approach to working with React, at least from the development side of things. If I open a component file, Codux gives me all the context I need to edit the code confidently because it <em>shows</em> me where and how the pieces are used throughout the app. Heck, it’s just as good (if not better!) at doing DevTools-y things than DevTools itself!</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/836092154"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>Visual boards, component trees, Git integration, debugging… Codux has them all and more, making it even more of a one-stop shop for all things related to a project. I compared Codux to CodePen earlier, but really it’s more like having CodePen, Figma, GitHub, VS Code, and even a prototyping app all in one place without ever having to leave your code.</p>

<h2 id="visually-explore-the-code">Visually Explore The Code</h2>

<p>Here’s something you might not expect from a tool like Codux. There are plenty of times when I make a change to a component, preview it, and then spot an issue somewhere else. There’s no easy way to find where the code for that issue is other than clicking into DevTools to inspect a certain element. I find myself constantly guessing because it takes too darn long to find the information or dig through the component library files to investigate upfront.</p>

<p>As such, I often have no idea how or if the code I am writing is going to affect something else in the app. Will changing this piece in one component impact the same piece in another component? By updating the component’s state or behavior here, am I inadvertently triggering another state that shouldn’t be affected?</p>

<p>Codux lets you navigate code <em>visually</em>. Your previews are right there in the app, and thanks to the tight integration between the visual and code editing capabilities, finding a piece of code is as simple as clicking on the element. Doing that will navigate you directly to the relevant code &mdash; both in the built-in code editor <em>and</em> your code editor. There’s absolutely no need to leave the code to go troubleshoot an issue or look up a computed value.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://codux.hopp.to/smashing">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="760"
			height="589"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png"
			
			sizes="100vw"
			alt="Cloudcash screenshot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That’s just one of those affordances that really polish up the developer experience. Anyone else might overlook something like this, but as a developer, you know how much saved time can add up with something like this.</p>

<h2 id="code-inspect-and-debug-together-at-last">Code, Inspect And Debug Together At Last</h2>

<p>There are a few other affordances available when selecting an element on the interactive stage on Codux:</p>

<ul>
<li>A <strong>style panel</strong> for editing CSS and trying different layouts. And, again, changes are made in real-time, both in the rendered preview and in <em>your</em> code, which is visible to you all the time &mdash; whether directly in Codux or in your IDE.</li>
<li>A <strong>property panel</strong> that provides easy access to all the selected properties of a component with visual controllers to modify them (and see the changes reflected directly in the code)</li>
<li>An <strong>environment panel</strong> that provides you with control over the rendering environment of the component, such as the screen or canvas size, as well as the styling for it.</li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://codux.hopp.to/smashing">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="458"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png"
			
			sizes="100vw"
			alt="Codux Elements Tree"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      See the panel on the left? That is the Codux Elements Tree, which reveals the actual DOM and logic responsible for rendering components, letting you validate structure, whether the component is static or dynamic. (<a href='https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="maybe-give-codux-a-spin">Maybe Give Codux A Spin</h2>

<p>It’s pretty rad that I can fire up a single app to access my component library, code, documentation, live previews, DOM inspector, and version control. If you would’ve tried explaining this to me <em>before</em> seeing Codux, I would’ve said that’s too much for one app to handle; it’d be a messy UI that’s more aspiration than it is a liberating center of development productivity.</p>

<p>No lying. That’s exactly what I thought when the Wix team told me about it. I didn’t even think it was a good idea to pack all that in one place.</p>

<p>But they did, and I was dead wrong. Codux is pretty awesome. And apparently, it will be even more awesome because the<a href="https://www.codux.com/faq"> FAQ talks about a bunch of new features</a> in the works, things like supporting full frameworks. The big one is an online version that will completely remove the need to set up development environments every time someone joins the team, or a stakeholder wants access to a working version of the app. Again, this is all in the works, but it goes to show how Codux is all about improving the <em>developer experience</em>.</p>

<p>And it’s not like you’re building a Wix site with it. Codux is its own thing &mdash; something that Wix built to get rid of their own pain points in the development process. It just so happens that their frustrations are the same that many of us in the community share, which makes Codux a legit consideration for any developer or team.</p>

<p>Oh, and it’s free. You can <a href="https://codux.hopp.to/smashing">download it right now</a>, and it supports Windows, Mac, and Linux. In other words, you can give it a spin without buying into anything.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Deepak Kumar</author><title>A Guide To Redux Toolkit With TypeScript</title><link>https://www.smashingmagazine.com/2023/05/guide-redux-toolkit-typescript/</link><pubDate>Wed, 10 May 2023 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/05/guide-redux-toolkit-typescript/</guid><description>The Redux Toolkit documentation calls the library a better way to write Redux logic for React apps and a simple and efficient toolkit for Redux development. In this article, you will learn about the Redux toolkit by building an app that tracks project issues.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/05/guide-redux-toolkit-typescript/" />
              <title>A Guide To Redux Toolkit With TypeScript</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>A Guide To Redux Toolkit With TypeScript</h1>
                  
                    
                    <address>Deepak Kumar</address>
                  
                  <time datetime="2023-05-10T10:00:00&#43;00:00" class="op-published">2023-05-10T10:00:00+00:00</time>
                  <time datetime="2023-05-10T10:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>If you are a React developer working on a complex application, you will need to use global state management for your app at some point. <a href="https://react-redux.js.org/">React Redux</a> is one of the most popular libraries for state management used by many developers. However, React Redux has a complex setup process that I’ve found inefficient, not to mention it requires a lot of boilerplate code. The official developer of Redux developed the <a href="https://redux-toolkit.js.org/introduction/getting-started">Redux Toolkit</a> to simplify the process.</p>

<p>This article is for those with enough knowledge of React and TypeScript to work with Redux.</p>

<h2 id="about-redux">About Redux</h2>

<p>Redux is the global state management library for React applications. If you have used <code>useState()</code> hooks for managing your app state, you will find it hard to access the state when you need it in the other parts of the application. With <code>useState()</code> hooks, the state can be passed from the parent component to the child, and you will be stuck with the problem of <a href="https://kentcdodds.com/blog/prop-drilling">prop drilling</a> if you need to pass it to multiple children. That’s where Redux comes in to manage the application state.</p>

<h2 id="introducing-redux-toolkit">Introducing Redux Toolkit</h2>

<p>Redux Toolkit is a set of opinionated and standardised tools that simplify application development using the Redux state management library.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aThe%20primary%20benefit%20of%20using%20Redux%20Toolkit%20is%20that%20it%20removes%20the%20overhead%20of%20writing%20a%20lot%20of%20boilerplates%20like%20you%e2%80%99d%20have%20to%20do%20with%20plain%20Redux.%0a&url=https://smashingmagazine.com%2f2023%2f05%2fguide-redux-toolkit-typescript%2f">
      
The primary benefit of using Redux Toolkit is that it removes the overhead of writing a lot of boilerplates like you’d have to do with plain Redux.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>It eliminates the need to write standard Redux setup code, such as defining actions, reducers, and store configuration, which can be a significant amount of code to write and maintain.</p>

<p>Jerry Navi has a <a href="https://www.smashingmagazine.com/2020/08/redux-real-world-application/">great tutorial that shows the full Redux setup process</a>.</p>

<h2 id="why-i-prefer-redux-toolkit-over-redux">Why I Prefer Redux Toolkit Over Redux</h2>

<p>The Redux Toolkit has several key features which make me use this library over plain Redux:</p>

<ol>
<li><strong>Defining reducers</strong><br />
With Redux Toolkit, you can specify a slice with a few lines of code to define a reducer instead of defining actions and reducers separately, like Redux.</li>
<li><strong>Immutability helpers</strong><br />
Redux Toolkit includes a set of utility functions that make it easy to update objects and arrays in an immutable way. This makes writing code that follows the Redux principles of immutability simpler.</li>
<li><strong>Built-in middleware</strong><br />
Redux Toolkit includes built-in middleware that can handle asynchronous request tasks.</li>
<li><strong>DevTools integration</strong><br />
Redux Toolkit includes integration with the Redux DevTools browser extension, which makes it easier to debug and analyse Redux code.</li>
</ol>

<h2 id="using-redux-toolkit-to-build-a-project-issue-tracker">Using Redux Toolkit To Build A Project Issue Tracker</h2>

<p>I think the best way to explain the value and benefits of using Redux Toolkit is simply to show them to you in a real-world context. So, let’s develop an app with it that is designed to create and track GitHub issues.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="366"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg"
			
			sizes="100vw"
			alt="Project issue tracker"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/guide-redux-toolkit-typescript/project-issue-tracker.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can follow along with the code examples as we go and <a href="https://github.com/smashingmagazine/project_issue_tracker/blob/master/src/components/ProjectCard.tsx">reference the full code anytime by grabbing it from GitHub</a>. There is also a <a href="https://master--relaxed-lamington-5fba83.netlify.app/">live deployment of this example</a> that you can check out.</p>

<p>Start creating a new React app with the following command:</p>

<pre><code class="language-bash">yarn create react-app project&#95;issue&#95;tracker --template typescript
</code></pre>

<p>This generates a folder for our project with the basic files we need for development. The <code>–template typescript</code> part of the command is used to add TypeScript to the stack.</p>

<p>Now, let’s install the dependencies packages required for our project and build the primary UI for the application before we implement Redux Toolkit. First, navigate to the <code>project_issue_tracker</code> project folder we just created:</p>

<pre><code class="language-bash">cd project&#95;issue&#95;tracker
</code></pre>

<p>Then run the following command to install <a href="https://mui.com/material-ui/">Material UI</a> and <a href="https://emotion.sh/docs/introduction">Emotion</a>, where the former is a design library we can use to style components, and the latter enables writing CSS in JavaScript files.</p>

<pre><code class="language-bash">yarn add @mui/material @emotion/react @emotion/styled
</code></pre>

<p>Now we can install Redix Toolkit and Redux itself:</p>

<pre><code class="language-bash">yarn add @reduxjs/toolkit react-redux
</code></pre>

<p>We have everything we need to start developing! We can start by building the user interface.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="/printed-books/image-optimization/">Image Optimization</a></strong>, Addy Osmani’s new practical guide to optimizing and delivering <strong>high-quality images</strong> on the web. Everything in one single <strong>528-pages</strong> book.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2c669cf1-c6ef-4c87-9901-018b04f7871f/image-optimization-shop-cover-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/87fd0cfa-692e-459c-b2f3-15209a1f6aa7/image-optimization-shop-cover-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="developing-the-user-interface">Developing The User Interface</h2>

<p>In this section, we will be developing the UI of the app. Open the main project folder and create a new <code>components</code> subfolder directly in the root. Inside this new folder, create a new file called <code>ProjectCard.tsx</code>. This is where we will write the code for a <code>ProjectCard</code> component that contains information about an open issue in the project issue tracker.</p>

<p>Let’s import some design elements from the Material UI package we installed to the new <code>/components/ProjectCard.tsx</code> file to get us started:</p>

<div class="break-out">
<pre><code class="language-javascript">import React from "react";
import { Typography, Grid, Stack, Paper} from "@mui/material";
interface IProps {
    issueTitle: string
}
const ProjectCard : React.FC&lt;IProps&gt; = ({ issueTitle }) =&gt; {
    return(
        &lt;div className="project&#95;card"&gt;
            &lt;Paper elevation={1} sx={{p: '10px', m:'1rem'}}&gt;
                &lt;Grid container spacing={2}&gt;
                    &lt;Grid item xs={12} md={6}&gt;
                        &lt;Stack spacing={2}&gt;
                            &lt;Typography variant="h6" sx={{fontWeight: 'bold'}}&gt;
                                Issue Title: {issueTitle}
                            &lt;/Typography&gt;
                            &lt;Stack direction='row' spacing={2}&gt;
                                &lt;Typography variant="body1"&gt;
                                    Opened: yesterday
                                &lt;/Typography&gt;
                                &lt;Typography variant="body1"&gt;
                                    Priority: medium
                                &lt;/Typography&gt;
                            &lt;/Stack&gt;
                        &lt;/Stack&gt;
                    &lt;/Grid&gt;
                &lt;/Grid&gt;
            &lt;/Paper&gt;
        &lt;/div&gt;
    )
}
export default ProjectCard;
</code></pre>
</div>

<p>This creates the project card that displays an issue title, issue priority level, and the time the issue was “opened.” Notice that we are using an <code>issueTitle</code> prop that will be passed to the <code>ProjectCard</code> component to render the issue with a provided title.</p>

<p>Now, let’s create the component for the app’s <code>HomePage</code> to display all the issues. We’ll add a small form to the page for submitting new issues that contain a text field for entering the issue name and a button to submit the form. We can do that by opening up the <code>src/HomePage.tsx</code> file in the project folder and importing React’s <code>useState</code> hook, a few more styled elements from Material UI, and the <code>ProjectCard</code> component we set up earlier:</p>

<div class="break-out">
<pre><code class="language-javascript">import React, { useState } from "react";
import { Box, Typography, TextField, Stack, Button } from "@mui/material";
import ProjectCard from "./components/ProjectCard";
const HomePage = () =&gt; {
    const [textInput, setTextInput] = useState('');
    const handleTextInputChange = (e:any) =&gt; {
        setTextInput(e.target.value);
    };
    return(
        &lt;div className="home&#95;page"&gt;
            &lt;Box sx={{ml: '5rem', mr: '5rem'}}&gt;
                &lt;Typography variant="h4" sx={{textAlign: 'center'}}&gt;
                    Project Issue Tracker
                &lt;/Typography&gt;
                &lt;Box sx={{display: 'flex'}}&gt;
                    &lt;Stack spacing={2}&gt;
                        &lt;Typography variant="h5"&gt;
                            Add new issue
                        &lt;/Typography&gt;
                        &lt;TextField 
                        id="outlined-basic" 
                        label="Title" 
                        variant="outlined" 
                        onChange={handleTextInputChange}
                        value={textInput}
                        /&gt;
                        &lt;Button variant="contained"&gt;Submit&lt;/Button&gt;
                    &lt;/Stack&gt;
                &lt;/Box&gt;
                &lt;Box sx={{ml: '1rem', mt: '3rem'}}&gt;
                    &lt;Typography variant="h5" &gt;
                        Opened issue
                    &lt;/Typography&gt;
                        &lt;ProjectCard issueTitle="Bug: Issue 1" /&gt;
                        &lt;ProjectCard issueTitle="Bug: Issue 2" /&gt;
                &lt;/Box&gt;
            &lt;/Box&gt;
        &lt;/div&gt;
    )
}
export default HomePage;
</code></pre>
</div>

<p>This results in a new <code>HomePage</code> component that a user can interact with to add new issues by entering an issue name in a form text input. When the issue is submitted, a new <code>ProjectCard</code> component is added to the <code>HomePage</code>, which acts as an index for viewing all open issues.</p>

<p>The only thing left for the interface is to render the <code>HomePage</code>, which we can do by adding it to the <code>App.tsx</code> file. <a href="https://github.com/smashingmagazine/project_issue_tracker/blob/master/src/App.tsx">The full code is available here on GitHub.</a></p>

<div class="partners__lead-place"></div>

<h2 id="using-redux-toolkit">Using Redux Toolkit</h2>

<p>Now that our UI is finalised, we can move on to implementing Redux Toolkit to manage the state of this app. We will use Redux Toolkit to manage the state of the <code>ProjectCard</code> list by <a href="https://redux-toolkit.js.org/tutorials/quick-start#create-a-redux-store">storing all the issues in a store</a> that can be accessed from anywhere in the application.</p>

<p>Before we move to the actual implementation, let’s understand a few Redux Toolkit concepts to help understand what we’re implementing:</p>

<ol>
<li><a href="https://redux-toolkit.js.org/usage/usage-with-typescript#createslice"><code>createSlice</code></a><br />
This function makes it easy to define the reducer, actions, and the <code>initialState</code> under one object. Unlike the plain redux, you don’t need to use a switch for actions and need to define the actions separately. This function accepts an object as a name (i.e., the name of the slice) and the initial state of the store and the reducer, where you define all the reducers along with their action types.</li>
<li><a href="https://redux-toolkit.js.org/usage/usage-with-typescript#configurestore"><code>configureStore</code></a><br />
This function is an abstraction for the Redux <code>createStore()</code> function. It removes the dependency of defining reducers separately and creating a store again. This way, the store is configured automatically and can be passed to the <a href="https://redux-toolkit.js.org/tutorials/rtk-query/#wrap-your-application-with-the-provider"><code>Provider</code></a>.</li>
<li><a href="https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk"><code>createAsyncThunk</code></a><br />
This function simplifies making asynchronous calls. It automatically dispatches many different actions for managing the state of the calls and provides a standardised way to handle errors.</li>
</ol>

<p>Let’s implement all of this! We will create the <code>issueReducer</code> with an <code>addIssue()</code> action that adds any new submitted issue to the <code>projectIssues</code> store. This can be done by creating a new file in <code>src/redux/</code> called <code>IssueReducer.ts</code> with this code:</p>

<div class="break-out">
<pre><code class="language-javascript">// Part 1
import { createSlice, PayloadAction } from "@reduxjs/toolkit"

// Part 2
export interface IssueInitialState {
    projectIssues: string[]
}
const initialState: IssueInitialState = {
    projectIssues: []
}

// Part 3
export const issueSlice = createSlice({
    name: 'issue',
    initialState,
    reducers: {
        addIssue: (state, action: PayloadAction&lt;string&gt;) =&gt; {
            state.projectIssues = [...state.projectIssues, action.payload]
        }
    }
})

// Part 4
export const { addIssue } = issueSlice.actions
export default issueSlice.reducer
</code></pre>
</div>

<p>Let’s understand each part of the code. First, we are importing the necessary functions from the Redux <code>@reduxjs/toolkit</code> package.</p>

<p>Then, we create the type definition of our initial state and initialise the <code>initialState</code> for the <code>issueReducer</code>. The <code>initialState</code> has a <code>projectIssues[]</code> list that will be used to store all the submitted issues. We can have as many properties defined in the <code>initialState</code> as we need for the application.</p>

<p>Thirdly, we are defining the <code>issueSlice</code> using Redux Toolkit’s <code>createSlice</code> function, which has the logic of the <code>issueReducer</code> as well as the different actions associated with it. <code>createSlice</code> accepts an object with a few properties, including:</p>

<ul>
<li><code>name</code>: the name of the slice,</li>
<li><code>initialState</code>: the initial state of the reducer function,</li>
<li><code>reducers</code>: an object that accepts different actions we want to define for our reducer.</li>
</ul>

<p>The slice name for the <code>issueReducer</code> is <code>issueSlice</code>. The <code>initalState</code> of it is defined, and a single <code>adIssue</code> action is associated with it. The <code>addIssue</code> action is dispatched whenever a new issue is submitted. We can have other actions defined, too, if the app requires it, but this is all we need for this example.</p>

<p>Finally, in the last part of the code, we export the actions associated with our reducer and the <code>issueSlice</code> reducer. We have fully implemented our <code>issueReducer</code>, which stores all the submitted issues by dispatching the <code>addIssue</code> action.</p>

<p>Now let’s configure the <code>issueReducer</code> in our store so we can use it in the app. Create a new file in <code>src/redux/</code> called <code>index.ts</code>, and add the following code:</p>

<pre><code class="language-javascript">import { configureStore } from "@reduxjs/toolkit";
import IssueReducer from "./IssueReducer";
export const store = configureStore({
    reducer: {
        issue: IssueReducer
    }
})
export type RootState = ReturnType&lt;typeof store.getState&gt;
export type AppDispatch = typeof store.dispatch
</code></pre>

<p>This code configures and creates the store using the <code>configureStore()</code> function that accepts a reducer where we can pass all of the different reducers.</p>

<p>We are done adding the reducer and configuring the store with Redux Toolkit. Let’s do the final step of passing the store to our app. Start by updating the <code>App.tsx</code> file to pass the store using the <code>Provider</code>:</p>

<pre><code class="language-javascript">import React from 'react';
import { Provider } from "react-redux"
import { store } from './redux';
import HomePage from './HomePage';
function App() {
    return (
        &lt;div className="App"&gt;
            &lt;Provider store={store}&gt;
                &lt;HomePage /&gt;
            &lt;/Provider&gt;
        &lt;/div&gt;
     );
}
export default App;
</code></pre>

<p>Here, you can see that we are importing the store and directly passing through the <code>Provider</code>. We don’t need to write anything extra to create a store or configure DevTools like we would using plain Redux. This is definitely one of the ways Redux Toolkit streamlines things.</p>

<p>OK, we have successfully set up a store and a reducer for our app with Redux Toolkit. Let’s use our app now and see if it works. To quickly sum things up, the <code>dispatch()</code> function is used to dispatch any actions to the store, and <code>useSelector()</code> is used for accessing any state properties.</p>

<p>We will dispatch the <code>addIssue</code> action when the form button is clicked:</p>

<pre><code class="language-javascript">const handleClick = () =&gt; {
    dispatch(addIssue(textInput))
}
</code></pre>

<p>To access the <code>projectIssue</code> list stored in our reducer store, we can make use of <code>useSelector()</code> like this:</p>

<pre><code class="language-javascript">const issueList = useSelector((state: RootState) =&gt; state.issue.projectIssues)
</code></pre>

<p>Finally, we can render all the issues by <code>map()</code>-ping the <code>issueList</code> to the <code>ProjectCard</code> component:</p>

<pre><code class="language-javascript">{
    issueList.map((issue) =&gt; {
        return(
            &lt;ProjectCard issueTitle={issue} /&gt;
        )
    })
}
</code></pre>

<p>The final code for <code>HomePage.tsx</code> looks like this:</p>

<div class="break-out">
<pre><code class="language-javascript">import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "./redux/index"
import { Box, Typography, TextField, Stack, Button } from "@mui/material";
import ProjectCard from "./components/ProjectCard";
import { addIssue } from "./redux/IssueReducer";
const HomePage = () =&gt; {
    const dispatch = useDispatch();
    const issueList = useSelector((state: RootState) =&gt; state.issue.projectIssues)
    const [textInput, setTextInput] = useState('');
    const handleTextInputChange = (e:any) =&gt; {
        setTextInput(e.target.value);
    };
    const handleClick = () =&gt; {
        dispatch(addIssue(textInput))
    }
    return(
        &lt;div className="home&#95;page"&gt;
            &lt;Box sx={{ml: '5rem', mr: '5rem'}}&gt;
                &lt;Typography variant="h4" sx={{textAlign: 'center'}}&gt;
                    Project Issue Tracker
                &lt;/Typography&gt;
                &lt;Box sx={{display: 'flex'}}&gt;
                    &lt;Stack spacing={2}&gt;
                        &lt;Typography variant="h5"&gt;
                            Add new issue
                        &lt;/Typography&gt;
                        &lt;TextField 
                        id="outlined-basic" 
                        label="Title" 
                        variant="outlined" 
                        onChange={handleTextInputChange}
                        value={textInput}
                        /&gt;
                        &lt;Button variant="contained" onClick={handleClick}&gt;Submit&lt;/Button&gt;
                    &lt;/Stack&gt;
                &lt;/Box&gt;
                &lt;Box sx={{ml: '1rem', mt: '3rem'}}&gt;
                    &lt;Typography variant="h5" &gt;
                        Opened issue
                    &lt;/Typography&gt;
                    {
                        issueList.map((issue) =&gt; {
                            return(
                                &lt;ProjectCard issueTitle={issue} /&gt;
                            )
                        })
                    }
                &lt;/Box&gt;
            &lt;/Box&gt;
        &lt;/div&gt;
    )
}
export default HomePage;
</code></pre>
</div>

<p>Now, when we add and submit an issue using the form, that issue will be rendered on the homepage.</p>

<p>This section covered how to define any reducer and how they’re used in the app. The following section will cover how Redux Toolkit makes asynchronous calls a relatively simple task.</p>

<div class="partners__lead-place"></div>

<h2 id="making-asynchronous-calls-with-redux-toolkit">Making Asynchronous Calls With Redux Toolkit</h2>

<p>We implemented our store to save and render any newly added issue to our app. What if we want to call GitHub API for any repository and list all the issues of it in our app? In this section, we will see how to use the <code>createAsyncThunk()</code> API with the slice to get data and render all the repository issues using an API call.</p>

<p>I always prefer to use the <code>createAsyncThunk()</code> API of the redux toolkit because it <strong>standardises the way different states are handled</strong>, such as <code>loading</code>, <code>error</code>, and <code>fulfilled</code>. Another reason is that we <strong>don’t need to add extra configurations for the middleware</strong>.</p>

<p>Let’s add the code for creating a <code>GithubIssue</code> reducer first before we break it down to understand what’s happening. Add a new <code>GithubIssueReducer.ts</code> file in the <code>/redux</code> folder and add this code:</p>

<div class="break-out">
<pre><code class="language-javascript">import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const fetchIssues = createAsyncThunk&lt;string[], void, { rejectValue: string }&gt;(
  "githubIssue/fetchIssues",
  async (&#95;, thunkAPI) =&gt; {
    try {
      const response = await fetch("https://api.github.com/repos/github/hub/issues");
      const data = await response.json();
      const issues = data.map((issue: { title: string }) =&gt; issue.title);
      return issues;
    } catch (error) {
      return thunkAPI.rejectWithValue("Failed to fetch issues.");
    }
  }
);
interface IssuesState {
  issues: string[];
  loading: boolean;
  error: string | null;
}
const initialState: IssuesState = {
  issues: [],
  loading: false,
  error: null,
};
export const issuesSliceGithub = createSlice({
  name: 'github&#95;issues',
  initialState,
  reducers: {},
  extraReducers: (builder) =&gt; {
    builder
      .addCase(fetchIssues.pending, (state) =&gt; {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchIssues.fulfilled, (state, action) =&gt; {
        state.loading = false;
        state.issues = action.payload;
      })
      .addCase(fetchIssues.rejected, (state, action) =&gt; {
        state.loading = false;
        state.error = action.error.message || 'Something went wrong';
      });
  },
});
export default issuesSliceGithub.reducer;
</code></pre>
</div>

<p>Let’s understand the <code>fetchIssues</code> part first:</p>

<ol>
<li>We are using the <code>createAsyncThunk()</code> API provided by the Redux Toolkit. It helps create asynchronous actions and handles the app’s loading and error states.</li>
<li>The action type name is the first argument passed to <code>createAsyncThunk()</code>. The specific action type name we have defined is <code>githubIssue/fetchIssues</code>.</li>
<li>The second argument is a function that returns a <code>Promise</code>, which resolves to the value that dispatches the action. This is when the asynchronous function fetches data from a GitHub API endpoint and maps the response data to a list of issue titles.</li>
<li>The third argument is an object that contains configuration options for the async thunk. In this case, we have specified that the async thunk will not be dispatched with any arguments (hence the <code>void</code> type) and that if the <code>Promise</code> returned by the async function is rejected, the async thunk will return an action with a rejected status along with a <code>rejectValue</code> property that contains the string “Failed to fetch issues.”</li>
</ol>

<p>When this action is dispatched, the API calls will be made, and the <code>githubIssuesList</code> data will be stored. We can follow this exact same sequence of steps to make any API calls we need.</p>

<p>The second section of the code is similar to what we used when we created the <code>issueSlice</code>, but with three differences:</p>

<ol>
<li><code>extraReducers</code><br />
This object contains the reducers logic for the reducers not defined in the <code>createSlice</code> reducers object. It takes a builder object where different cases can be added using <code>addCase</code> for specific action types.</li>
<li><code>addCase</code><br />
This method on the builder object creates a new case for the reducer function.</li>
<li><strong>API call states</strong><br />
The callback function passed to the <code>addCase</code> method is dispatched by <code>createAsyncThunk()</code>, which updates the different store objects based on the API call states (<code>pending</code>, <code>fulfilled</code>, and <code>error</code>).</li>
</ol>

<p>We can now use the <code>GithubIssue</code> reducer actions and the store in our app. Let’s add the <code>GithubIssueReducer</code> to our store first. Update the <code>/redux/index.ts</code> file with this code:</p>

<pre><code class="language-javascript">
import { configureStore } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";
import IssueReducer from "./IssueReducer";
import GithubIssueReducer from "./GithubIssueReducer";
export const store = configureStore({
    reducer: {
        issue: IssueReducer,
        githubIssue: GithubIssueReducer
    }
})
export type RootState = ReturnType&lt;typeof store.getState&gt;
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () =&gt; useDispatch&lt;AppDispatch&gt;()
</code></pre>

<p>We just added the <code>GithubIssueReducer</code> to our store with the name mapped to <code>githubIssue</code>. We can now use this reducer in our <code>HomePage</code> component to dispatch the <code>fetchIssues()</code> and populate our page with all the issues received from the GitHub API repo.</p>

<div class="break-out">
<pre><code class="language-javascript">import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { useAppDispatch, RootState, AppDispatch } from "./redux/index";
import { Box, Typography, TextField, Stack, Button } from "@mui/material";
import ProjectCard from "./components/ProjectCard";
import { addIssue } from "./redux/IssueReducer";
import { fetchIssues } from "./redux/GithubIssueReducer";
const HomePage = () =&gt; {
    const dispatch: AppDispatch = useAppDispatch();
    const [textInput, setTextInput] = useState('');
    const githubIssueList = useSelector((state: RootState) =&gt; state.githubIssue.issues)
    const loading = useSelector((state: RootState) =&gt; state.githubIssue.loading);
    const error = useSelector((state: RootState) =&gt; state.githubIssue.error);
    useEffect(() =&gt; {
        dispatch(fetchIssues())
      }, [dispatch]);
    
    if (loading) {
      return &lt;div&gt;Loading...&lt;/div&gt;;
    }
  
    if (error) {
      return &lt;div&gt;Error: {error}&lt;/div&gt;;
    }
    const handleTextInputChange = (e:any) =&gt; {
        setTextInput(e.target.value);
    };
    const handleClick = () =&gt; {
        console.log(textInput)
        dispatch(addIssue(textInput))
    }
    return(
        &lt;div className="home&#95;page"&gt;
            &lt;Box sx={{ml: '5rem', mr: '5rem'}}&gt;
                &lt;Typography variant="h4" sx={{textAlign: 'center'}}&gt;
                    Project Issue Tracker
                &lt;/Typography&gt;
                &lt;Box sx={{display: 'flex'}}&gt;
                    &lt;Stack spacing={2}&gt;
                        &lt;Typography variant="h5"&gt;
                            Add new issue
                        &lt;/Typography&gt;
                        &lt;TextField 
                        id="outlined-basic" 
                        label="Title" 
                        variant="outlined" 
                        onChange={handleTextInputChange}
                        value={textInput}
                        /&gt;
                        &lt;Button variant="contained" onClick={handleClick}&gt;Submit&lt;/Button&gt;
                    &lt;/Stack&gt;
                &lt;/Box&gt;
                &lt;Box sx={{ml: '1rem', mt: '3rem'}}&gt;
                    &lt;Typography variant="h5" &gt;
                        Opened issue
                    &lt;/Typography&gt;
                    {
                        githubIssueList?.map((issue : string) =&gt; {
                            return(
                                &lt;ProjectCard issueTitle={issue} /&gt;
                            )
                        })
                    }
                &lt;/Box&gt;
            &lt;/Box&gt;
        &lt;/div&gt;
    )
}
export default HomePage;
</code></pre>
</div>

<p>This updates the code in <code>HomePage.tsx</code> with two minor changes:</p>

<ol>
<li>We dispatch <code>fetchIssue</code> and use the  <code>createAsync()</code> action to make the API calls under the <code>useEffect</code> hook.</li>
<li>We use the <code>loading</code> and <code>error</code> states when the component renders.</li>
</ol>

<p>Now, when loading the app, you will first see the “Loading” text rendered, and once the API call is fulfilled, the <code>issuesList</code> will be populated with all the titles of GitHub issues fetched from the repo.</p>

<p>Once again, the complete code for this project <a href="https://github.com/smashingmagazine/project_issue_tracker">can be found on GitHub</a>. You can also <a href="https://master--relaxed-lamington-5fba83.netlify.app/">check out a live deployment of the app</a>, which displays all the issues fetched from GitHub.</p>

<h2 id="conclusion">Conclusion</h2>

<p>There we have it! We used Redux Toolkit in a React TypeScript application to build a fully functional project issue tracker that syncs with GitHub and allows us to create new issues directly from the app.</p>

<p>We learned many of the foundational concepts of Redux Toolkit, such as defining reducers, immutability helpers, built-in middleware, and DevTools integration. I hope you feel powered to use Redux Toolkit effectively in your projects. With Redux Toolkit, you can improve the performance and scalability of your React applications by effectively managing the global state.</p>

<h3 id="further-reading-on-smashing-magazine">Further Reading on Smashing Magazine</h3>

<ul>
<li><a href="https://www.smashingmagazine.com/2020/12/how-redux-reducers-work/">How Redux Reducers Work</a>, Fortune Ikechi</li>
<li><a href="https://www.smashingmagazine.com/2018/07/redux-designers-guide/">What Is Redux: A Designer’s Guide</a>, Linton Ye</li>
<li><a href="https://www.smashingmagazine.com/2021/01/dynamic-static-typing-typescript/">Dynamic Static Typing In TypeScript</a>, Stefan Baumgartner</li>
<li><a href="https://www.smashingmagazine.com/2021/11/deep-dive-into-serverless-ui-typescript/">A Deep Dive Into Serverless UI With TypeScript</a>, Ikeh Akinyemi</li>
<li><a href="https://www.smashingmagazine.com/2020/08/redux-real-world-application/">Setting Up Redux For Use In A Real-World Application</a>, Jerry Navi</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Jessica Joseph</author><title>The Safest Way To Hide Your API Keys When Using React</title><link>https://www.smashingmagazine.com/2023/05/safest-way-hide-api-keys-react/</link><pubDate>Mon, 08 May 2023 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/05/safest-way-hide-api-keys-react/</guid><description>Want to make sure your API keys are safe and sound when working with React? Jessica Joseph’s got you covered! She will show you the best ways to hide your API keys, from using environment variables to building your own back-end proxy server.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/05/safest-way-hide-api-keys-react/" />
              <title>The Safest Way To Hide Your API Keys When Using React</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The Safest Way To Hide Your API Keys When Using React</h1>
                  
                    
                    <address>Jessica Joseph</address>
                  
                  <time datetime="2023-05-08T13:00:00&#43;00:00" class="op-published">2023-05-08T13:00:00+00:00</time>
                  <time datetime="2023-05-08T13:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Back in the day, developers had to write all sorts of custom code to get different applications to communicate with each other. But, these days, <a href="https://developer.mozilla.org/en-US/docs/Glossary/API">Application Programming Interfaces</a> (APIs) make it so much easier. APIs provide you with everything you need to interact with different applications smoothly and efficiently, most commonly where one application requests data from the other application.</p>

<p>While APIs offer numerous benefits, they also present a significant risk to your application security. That is why it is essential to learn about their vulnerabilities and how to protect them. In this article, we’ll delve into the wonderful world of API keys, discuss why you should protect your API keys, and look at the best ways to do so when using React.</p>

<h2 id="what-are-api-keys">What Are API Keys?</h2>

<p>If you recently signed up for an API, you will get an API key. Think of API keys as secret passwords that prove to the provider that it is you or your app that’s attempting to access the API. While some APIs are free, others charge a cost for access, and because most API keys have zero expiration date, it is frightening not to be concerned about the safety of your keys.</p>

<h2 id="why-do-api-keys-need-to-be-protected">Why Do API Keys Need To Be Protected?</h2>

<p>Protecting your API keys is crucial for guaranteeing the security and integrity of your application. Here are some reasons why you ought to guard your API keys:</p>

<ul>
<li><strong>To prevent unauthorized API requests.</strong><br />
If someone obtains your API key, they can use it to make unauthorized requests, which could have serious ramifications, especially if your API contains sensitive data.</li>
<li><strong>Financial insecurity.</strong><br />
Some APIs come with a financial cost. And if someone gains access to your API key and exceeds your budget requests, you may be stuck with a hefty bill which could cost you a ton and jeopardize your financial stability.</li>
<li><strong>Data theft, manipulation, or deletion.</strong><br />
If a malicious person obtains access to your API key, they may steal, manipulate, delete, or use your data for their purposes.</li>
</ul>

<h2 id="best-practices-for-hiding-api-keys-in-a-react-application">Best Practices For Hiding API Keys In A React Application</h2>

<p>Now that you understand why API keys must be protected, let’s take a look at some methods for hiding API keys and how to integrate them into your React application.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="/printed-books/image-optimization/">Image Optimization</a></strong>, Addy Osmani’s new practical guide to optimizing and delivering <strong>high-quality images</strong> on the web. Everything in one single <strong>528-pages</strong> book.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2c669cf1-c6ef-4c87-9901-018b04f7871f/image-optimization-shop-cover-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/87fd0cfa-692e-459c-b2f3-15209a1f6aa7/image-optimization-shop-cover-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h3 id="environment-variables">Environment Variables</h3>

<p><a href="https://en.wikipedia.org/wiki/Environment_variable">Environment variables</a> (<code>env</code>) are used to store information about the environment in which a program is running. It enables you to hide sensitive data from your application code, such as API keys, tokens, passwords, and just any other data you’d like to keep hidden from the public.</p>

<p>One of the most popular <code>env</code> packages you can use in your React application to hide sensitive data is the <a href="https://github.com/motdotla/dotenv"><code>dotenv</code></a> package. To get started:</p>

<ol>
  <li>Navigate to your react application directory and run the command below.<br />
<pre><code class="language-bash">npm install dotenv --save
</code></pre>
  </li>
  <li>Outside of the <code>src</code> folder in your project root directory, create a new file called <code>.env</code>.<br /><br />













<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="378"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png"
			
			sizes="100vw"
			alt="A screenshot with a highlighted env file in the project root directory"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/safest-way-hide-api-keys-react/1-env-file-project-root-directory.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
  <li>In your <code>.env</code> file, add the API key and its corresponding value in the following format:<br />
<pre><code class="language-bash">// for CRA applications
REACT&#95;APP&#95;API&#95;KEY = A1234567890B0987654321C ------ correct

// for Vite applications
VITE&#95;SOME&#95;KEY = 12345GATGAT34562CDRSCEEG3T  ------ correct
</code></pre>
</li>
  <li>Save the <code>.env</code> file and avoid sharing it publicly or committing it to version control.</li>
  <li>You can now use the <code>env</code> object to access your environment variables in your React application.<br />
<pre><code class="language-bash">// for CRA applications
'X-RapidAPI-Key':process.env.REACT&#95;APP&#95;API&#95;KEY
// for Vite  applications
'X-RapidAPI-Key':import.meta.env.VITE&#95;SOME&#95;KEY
</code></pre>
</li>
  <li>Restart your application for the changes to take effect.</li>
</ol>

<p>However, running your project on your local computer is only the beginning. At some point, you may need to upload your code to GitHub, which could potentially expose your <code>.env</code> file. So what to do then? You can consider using the <code>.gitignore</code> file to hide it.</p>

<h3 id="the-gitignore-file">The <code>.gitignore</code> File</h3>

<p>The <code>.gitignore</code> file is a text file that instructs Git to ignore files that have not yet been added to the repository when it’s pushed to the repo. To do this, add the <code>.env</code> to the <code>.gitignore</code> file before moving forward to staging your commits and pushing your code to GitHub.</p>

<pre><code class="language-bash">// .gitignore
# dependencies
/node_modules
/.pnp
.pnp.js

# api keys
.env
</code></pre>

<p>Keep in mind that at any time you decide to host your projects using any hosting platforms, like <a href="https://vercel.com/">Vercel</a> or <a href="https://www.netlify.com/">Netlify</a>, you are to provide your environment variables in your project settings and, soon after, redeploy your app to view the changes.</p>

<div class="partners__lead-place"></div>

<h3 id="back-end-proxy-server">Back-end Proxy Server</h3>

<p>While environment variables can be an excellent way to protect your API keys, remember that they can still be compromised. Your keys can still be stolen if an attacker inspects your bundled code in the browser. So, what then can you do? Use a back-end proxy server.</p>

<p>A back-end proxy server acts as an intermediary between your client application and your server application. Instead of directly accessing the API from the front end, the front end sends a request to the back-end proxy server; the proxy server then retrieves the API key and makes the request to the API. Once the response is received, it removes the API key before returning the response to the front end. This way, your API key will never appear in your front-end code, and no one will be able to steal your API key by inspecting your code. Great! Now let’s take a look at how we can go about this:</p>

<ol>
<li><strong>Install necessary packages.</strong><br />To get started, you need to install some packages such as <a href="http://expressjs.com/">Express</a>, <a href="https://github.com/expressjs/cors">CORS</a>, <a href="https://axios-http.com/">Axios</a>, and <a href="https://nodemon.io/">Nodemon</a>. To do this, navigate to the directory containing your React project and execute the following command:<br />
<pre><code class="language-bash">npm install express cors axios nodemon
</code></pre>
</li>
<li><strong>Create a back-end server file.</strong><br />In your project root directory, outside your <code>src</code> folder, create a JavaScript file that will contain all of your requests to the API.<br /><br />













<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="365"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png"
			
			sizes="100vw"
			alt="A screenshot with a highlighted server.js file in the project root directory"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/safest-way-hide-api-keys-react/2-javascript-file-project-root-directory.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
<li><strong>Initialize dependencies and set up an endpoint.</strong><br />In your backend server file, initialize the installed dependencies and set up an endpoint that will make a <code>GET</code> request to the third-party API and return the response data on the listened port. Here is an example code snippet:<br />
<pre><code class="language-javascript">// defining the server port
const port = 5000

// initializing installed dependencies
const express = require('express')
require('dotenv').config()
const axios = require('axios')
const app = express()
const cors = require('cors')
app.use(cors())

// listening for port 5000
app.listen(5000, ()=&gt; console.log(`Server is running on ${port}` ))

// API request
app.get('/', (req,res)=&gt;{    
    const options = {
        method: 'GET',
        url: 'https://wft-geo-db.p.rapidapi.com/v1/geo/adminDivisions',
        headers: {
            'X-RapidAPI-Key':process.env.REACT&#95;APP&#95;API&#95;KEY,
            'X-RapidAPI-Host': 'wft-geo-db.p.rapidapi.com'
        }
   };
   
    axios.request(options).then(function (response) {
        res.json(response.data);
    }).catch(function (error) {
        console.error(error);
    });
}
</code></pre>
</li>
<li>Add a script tag in your <code>package.json</code> file that will run the back-end proxy server.<br /><br />













<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="229"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png"
			
			sizes="100vw"
			alt="A screenshot with a script tag in a package.json file"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/safest-way-hide-api-keys-react/3-script-tag-package-json-file.png'>Large preview</a>)
    </figcaption>
  
</figure>
</li>
<li>Kickstart the back-end server by running the command below and then, in this case, navigate to <code>localhost:5000</code>.<br />
<pre><code class="language-bash">npm run start:backend
</code></pre>
</li>
<li>Make a request to the backend server (<code>http://localhost:5000/</code>) from the front end instead of directly to the API endpoint. Here’s an illustration:<br />
<pre><code class="language-javascript">import axios from "axios";
import {useState, useEffect} from "react"

function App() {

  const [data, setData] = useState(null)

  useEffect(()=&gt;{
    const options = {
      method: 'GET',
      url: "http://localhost:5000",
    }
    axios.request(options)
    .then(function (response) {
        setData(response.data.data)
    })
    .catch(function (error) {
        console.error(error);
    })  
  }, [])

  console.log(data)

  return (
    &lt;main className="App"&gt;
    &lt;h1&gt;How to Create a Backend Proxy Server for Your API Keys&lt;/h1&gt;
     {data && data.map((result)=&gt;(
      &lt;section key ={result.id}&gt;
        &lt;h4&gt;Name:{result.name}&lt;/h4&gt;
        &lt;p&gt;Population:{result.population}&lt;/p&gt;
        &lt;p&gt;Region:{result.region}&lt;/p&gt;
        &lt;p&gt;Latitude:{result.latitude}&lt;/p&gt;
        &lt;p&gt;Longitude:{result.longitude}&lt;/p&gt;
      &lt;/section>
    ))}
    &lt;/main&gt;
  )
}
export default App;
</code></pre>
</li>
</ol>

<p>Okay, there you have it! By following these steps, you&rsquo;ll be able to hide your API keys using a back-end proxy server in your React application.</p>

<h3 id="key-management-service">Key Management Service</h3>

<p>Even though environment variables and the back-end proxy server allow you to safely hide your API keys online, you are still not completely safe. You may have friends or foes around you who can access your computer and steal your API key. That is why data encryption is essential.</p>

<p>With a key management service provider, you can encrypt, use, and manage your API keys. There are tons of key management services that you can integrate into your React application, but to keep things simple, I will only mention a few:</p>

<ul>
<li><strong>AWS Secrets Manager</strong><br />
The <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html">AWS Secrets Manager</a> is a secret management service provided by Amazon Web Services. It enables you to store and retrieve secrets such as database credentials, API keys, and other sensitive information programmatically via API calls to the AWS Secret Manager service. There are a ton of resources that can get you started in no time.</li>
<li><strong>Google Cloud Secret Manager</strong><br />
The <a href="https://cloud.google.com/secret-manager">Google Cloud Secret Manager</a> is a key management service provided and fully managed by the Google Cloud Platform. It is capable of storing, managing, and accessing sensitive data such as API keys, passwords, and certificates. The best part is that it seamlessly integrates with Google’s back-end-as-a-service features, making it an excellent choice for any developer looking for an easy solution.</li>
<li><strong>Azure Key Vault</strong><br />
The <a href="https://azure.microsoft.com/en-us/products/key-vault/">Azure Key Vault</a> is a cloud-based service provided by Microsoft Azure that allows you to seamlessly store and manage a variety of secrets, including passwords, API keys, database connection strings, and other sensitive data that you don’t want to expose directly in your application code.</li>
</ul>

<p>There are more key management services available, and you can choose to go with any of the ones mentioned above. But if you want to go with a service that wasn’t mentioned, that’s perfectly fine as well.</p>

<div class="partners__lead-place"></div>

<h2 id="tips-for-ensuring-security-for-your-api-keys">Tips For Ensuring Security For Your API Keys</h2>

<p>You have everything you need to keep your API keys and data secure. So, if you have existing projects in which you have accidentally exposed your API keys, don’t worry; I&rsquo;ve put together some handy tips to help you identify and fix flaws in your React application codebase:</p>

<ol>
<li>Review your existing codebase and identify any hardcoded API key that needs to be hidden.</li>
<li>Use environment variables with <code>.gitignore</code> to securely store your API keys. This will help to prevent accidental exposure of your keys and enable easier management across different environments.</li>
<li>To add an extra layer of security, consider using a back-end proxy server to protect your API keys, and, for advanced security needs, a key management tool would do the job.</li>
</ol>

<h2 id="conclusion">Conclusion</h2>

<p>Awesome! You can now protect your API keys in React like a pro and be confident that your application data is safe and secure. Whether you use environment variables, a back-end proxy server, or a key management tool, they will keep your API keys safe from prying eyes.</p>

<h3 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2021/12/protect-api-key-production-nextjs-api-route/">How To Protect Your API Key In Production With Next.js API Route</a>”, Caleb Olojo</li>
<li>“<a href="https://www.smashingmagazine.com/2021/10/react-apis-building-flexible-components-typescript/">Useful React APIs For Building Flexible Components With TypeScript</a>”, Gaurav Khanna</li>
<li>“<a href="https://www.smashingmagazine.com/2021/08/state-management-nextjs/">State Management In Next.js</a>”, Atila Fassina</li>
<li>“<a href="https://www.smashingmagazine.com/2023/03/internationalization-nextjs-13-react-server-components/">Internationalization In Next.js 13 With React Server Components</a>”, Jan Amann</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Jan Amann</author><title>Internationalization In Next.js 13 With React Server Components</title><link>https://www.smashingmagazine.com/2023/03/internationalization-nextjs-13-react-server-components/</link><pubDate>Thu, 16 Mar 2023 20:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/03/internationalization-nextjs-13-react-server-components/</guid><description>Based on an example of a multilingual app that displays street photography images from Unsplash, Jan Amann explores &lt;code>next-intl&lt;/code> to implement all internationalization needs in React Server Components and shares a technique for introducing interactivity with a minimalistic client-side footprint.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/03/internationalization-nextjs-13-react-server-components/" />
              <title>Internationalization In Next.js 13 With React Server Components</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Internationalization In Next.js 13 With React Server Components</h1>
                  
                    
                    <address>Jan Amann</address>
                  
                  <time datetime="2023-03-16T20:00:00&#43;00:00" class="op-published">2023-03-16T20:00:00+00:00</time>
                  <time datetime="2023-03-16T20:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>With the introduction of <a href="https://beta.nextjs.org/docs/getting-started">Next.js 13</a> and the beta release of the App Router, React Server Components became publicly available. This new paradigm allows components that don’t require React’s interactive features, such as <code>useState</code> and <code>useEffect</code>, to remain server-side only.</p>

<p>One area that benefits from this new capability is <strong>internationalization</strong>. Traditionally, internationalization requires a tradeoff in performance as loading translations results in larger client-side bundles and using message parsers impacts the client runtime performance of your app.</p>

<p>The promise of <strong>React Server Components</strong> is that we can have our cake and eat it too. If internationalization is implemented entirely on the server side, we can achieve new levels of performance for our apps, leaving the client side for interactive features. But how can we work with this paradigm when we need interactively-controlled states that should be reflected in internationalized messages?</p>

<p>In this article, we’ll explore a multilingual app that displays street photography images from Unsplash. We’ll use <a href="https://next-intl-docs.vercel.app/"><code>next-intl</code></a> to implement all our internationalization needs in React Server Components, and we’ll look at a technique for introducing interactivity with a minimalistic client-side footprint.</p>














<figure class="
  
  
  ">
  
    <a href="https://street-photography-viewer.vercel.app/en">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="680"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png"
			
			sizes="100vw"
			alt="App final framed"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      You can also check the <a href='https://street-photography-viewer.vercel.app/en'>interactive demo</a>. (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/13-app-final-framed.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="fetching-photos-from-unsplash">Fetching Photos From Unsplash</h2>

<p>A key benefit of Server Components is the ability to fetch data directly from inside components via <code>async</code>/<code>await</code>. We can use this to fetch the photos from Unsplash in our page component.</p>

<p>But first, we need to create our API client based on the official Unsplash SDK.</p>

<pre><code class="language-javascript">import {createApi} from 'unsplash-js';

export default createApi({
  accessKey: process.env.UNSPLASH&#95;ACCESS&#95;KEY
});
</code></pre>

<p>Once we have our Unsplash API client, we can use it in our page component.</p>

<pre><code class="language-javascript">import {OrderBy} from 'unsplash-js';
import UnsplashApiClient from './UnsplashApiClient';

export default async function Index() {
  const topicSlug = 'street-photography';

  const [topicRequest, photosRequest] = await Promise.all([
    UnsplashApiClient.topics.get({topicIdOrSlug: topicSlug}),
    UnsplashApiClient.topics.getPhotos({
      topicIdOrSlug: topicSlug,
      perPage: 4
    })
  ]);

  return (
    &lt;PhotoViewer
      coverPhoto={topicRequest.response.cover&#95;photo}
      photos={photosRequest.response.results}
    /&gt;
  );
}
</code></pre>

<p><strong>Note:</strong> <em>We use <code>Promise.all</code> to invoke both requests that we need to make in parallel. This way, we avoid a request waterfall.</em></p>

<p>At this point, our app renders a simple photo grid.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="602"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png"
			
			sizes="100vw"
			alt="An app which renders a simple photo grid"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/2-app-basic-photo-grid.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The app currently uses hard-coded English labels, and the dates of the photos are displayed as timestamps, which is not very user-friendly (yet).</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p><p>Meet <a data-instant href="/the-smashing-newsletter/"><strong>Smashing Email Newsletter</strong></a> with useful tips on front-end, design &amp; UX. Subscribe and <strong>get “Smart Interface Design Checklists”</strong> &mdash; a <strong>free PDF deck</strong> with 150+ questions to ask yourself when designing and building almost <em>anything</em>.</p><div><section class="nlbf"><form action="//smashingmagazine.us1.list-manage.com/subscribe/post?u=16b832d9ad4b28edf261f34df&amp;id=a1666656e0" method="post"><div class="nlbwrapper"><label for="mce-EMAIL-hp" class="sr-only">Your (smashing) email</label><div class="nlbgroup"><input type="email" name="EMAIL" class="nlbf-email" id="mce-EMAIL-hp" placeholder="Your email">
<input type="submit" value="Meow!" name="subscribe" class="nlbf-button"></div></div></form><style>.c-garfield-the-cat .nlbwrapper{margin-bottom: 0;}.nlbf{display:flex;padding-bottom:.25em;padding-top:.5em;text-align:center;letter-spacing:-.5px;color:#fff;font-size:1.15em}.nlbgroup:hover{box-shadow:0 1px 7px -5px rgba(50,50,93,.25),0 3px 16px -8px rgba(0,0,0,.3),0 -6px 16px -6px rgba(0,0,0,.025)}.nlbf .nlbf-button,.nlbf .nlbf-email{flex-grow:1;flex-shrink:0;width:auto;margin:0;padding:.75em 1em;border:0;border-radius:11px;background:#fff;font-size:1em;box-shadow:none}.promo-box .nlbf-button:focus,.promo-box input.nlbf-email:active,.promo-box input.nlbf-email:focus{box-shadow:none}.nlbf-button:-ms-input-placeholder,.nlbf-email:-ms-input-placeholder{color:#777;font-style:italic}.nlbf-button::-webkit-input-placeholder,.nlbf-email::-webkit-input-placeholder{color:#777;font-style:italic}.nlbf-button:-ms-input-placeholder,.nlbf-button::-moz-placeholder,.nlbf-button::placeholder,.nlbf-email:-ms-input-placeholder,.nlbf-email::-moz-placeholder,.nlbf-email::placeholder{color:#777;font-style:italic}.nlbf .nlbf-button{transition:all .2s ease-in-out;color:#fff;background-color:#0168b8;font-weight:700;box-shadow:0 1px 1px rgba(0,0,0,.3);width:100%;border:0;border-left:1px solid #ddd;flex:2;border-top-left-radius:0;border-bottom-left-radius:0}.nlbf .nlbf-email{border-top-right-radius:0;border-bottom-right-radius:0;width:100%;flex:4;min-width:150px}@media all and (max-width:650px){.nlbf .nlbgroup{flex-wrap:wrap;box-shadow:none}.nlbf .nlbf-button,.nlbf .nlbf-email{border-radius:11px;border-left:none}.nlbf .nlbf-email{box-shadow:0 13px 27px -5px rgba(50,50,93,.25),0 8px 16px -8px rgba(0,0,0,.3),0 -6px 16px -6px rgba(0,0,0,.025);min-width:100%}.nlbf .nlbf-button{margin-top:1em;box-shadow:0 1px 1px rgba(0,0,0,.5)}}.nlbf .nlbf-button:active,.nlbf .nlbf-button:focus,.nlbf .nlbf-button:hover{cursor:pointer;color:#fff;background-color:#0168b8;border-color:#dadada;box-shadow:0 1px 1px rgba(0,0,0,.3)}.nlbf .nlbf-button:active,.nlbf .nlbf-button:focus{outline:0!important;text-shadow:1px 1px 1px rgba(0,0,0,.3);box-shadow:inset 0 3px 3px rgba(0,0,0,.3)}.nlbgroup{display:flex;box-shadow:0 13px 27px -5px rgba(50,50,93,.25),0 8px 16px -8px rgba(0,0,0,.3),0 -6px 16px -6px rgba(0,0,0,.025);border-radius:11px;transition:box-shadow .2s ease-in-out}.nlbwrapper{display:flex;flex-direction:column;justify-content:center}.nlbf form{width:100%}.nlbf .nlbgroup{margin:0}.nlbcaption{font-size:.9em;line-height:1.5em;color:#fff;border-radius:11px;padding:.5em 1em;display:inline-block;background-color:#0067b859;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.wf-loaded-stage2 .nlbf .nlbf-button{font-family:Mija}.mts{margin-top: 5px !important;}.mbn{margin-bottom: 0 !important;}</style></section><p class="mts mbn"><small class="promo-box__footer mtm block grey"><em>Once a week. Useful tips on <a href="https://www.smashingmagazine.com/the-smashing-newsletter/">front-end &amp; UX</a>. Trusted by 207.000 friendly folks.</em></small></p></div></p>
</div>
</div>
<div class="feature-panel-right-col">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-firechat.svg"
    alt="Feature Panel"
    width="310"
    height="400"
/>

</div>

<p></div>
</aside>
</div></p>

<h2 id="adding-internationalization-with-next-intl">Adding Internationalization With <code>next-intl</code></h2>

<p>In addition to English, we’d like our app to be available in Spanish. Support for Server Components is currently in beta for <a href="https://next-intl-docs.vercel.app/"><code>next-intl</code></a>, so we can use <a href="https://next-intl-docs.vercel.app/docs/next-13/server-components">the installation instructions for the latest beta</a> to set up our app for internationalization.</p>

<h3 id="formatting-dates">Formatting Dates</h3>

<p>Aside from adding a second language, we’ve already found that the app doesn’t adapt well to English users because the dates should be formatted. To achieve a good user experience, we’d like to tell the user the relative time when the photo was uploaded (e.g., “8 days ago”).</p>

<p>Once  <code>next-intl</code> is set up, we can fix the formatting by using the <code>format.relativeTime</code> function in the component that renders each photo.</p>

<pre><code class="language-javascript">import {useFormatter} from 'next-intl';

export default function PhotoGridItem({photo}) {
  const format = useFormatter();
  const updatedAt = new Date(photo.updated&#95;at);

  return (
    &lt;a href={photo.links.html}&gt;
        {/&#42; ... &#42;/}
        &lt;p&gt;{format.relativeTime(updatedAt)}&lt;/p&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  );
}
</code></pre>

<p>Now the date when a photo has been updated is easier to read.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="547"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png"
			
			sizes="100vw"
			alt="An app’s photo time with the formatted date"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/3-app-photo-item-date-formatted.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Hint:</strong> <em>In a traditional React app that renders on both the server and client side, it can be quite a challenge to ensure that the displayed relative date is in sync across the server and client. Since these are different environments and may be in different time zones, you need to configure a mechanism to transfer the server time to the client side. By performing the formatting only on the server side, we don’t have to worry about this problem in the first place.</em></p>

<h3 id="hola-translating-our-app-to-spanish">¡Hola! 👋 Translating Our App To Spanish</h3>

<p>Next, we can replace the static labels in the header with localized messages. These labels are passed as props from the <code>PhotoViewer</code> component, so this is our chance to introduce dynamic labels via the <code>useTranslations</code> hook.</p>

<pre><code class="language-javascript">import {useTranslations} from 'next-intl';

export default function PhotoViewer(/&#42; ... &#42;/) {
  const t = useTranslations('PhotoViewer');

  return (
    &lt;&gt;
      &lt;Header
        title={t('title')}
        description={t('description')}
      /&gt;
      {/&#42; ... &#42;/}
    &lt;/&gt;
  );
}
</code></pre>

<p>For each internationalized label we add, we need to make sure that there is an appropriate entry set up for all languages.</p>

<div class="break-out">

<pre><code class="language-javascript">// en.json
{
  "PhotoViewer": {
    "title": "Street photography",
    "description": "Street photography captures real-life moments and human interactions in public places. It is a way to tell visual stories and freeze fleeting moments of time, turning the ordinary into the extraordinary."
  }
}
</code></pre>
</div>

<div class="break-out">

<pre><code class="language-javascript">// es.json
{
  "PhotoViewer": {
    "title": "Street photography",
    "description": "La fotografía callejera capta momentos de la vida real y interacciones humanas en lugares públicos. Es una forma de contar historias visuales y congelar momentos fugaces del tiempo, convirtiendo lo ordinario en lo extraordinario."
  }
}
</code></pre>
</div>

<p><strong>Tip:</strong> <em><a href="https://next-intl-docs.vercel.app/docs/usage/typescript"><code>next-intl</code> provides a TypeScript integration</a> that helps you ensure that you’re only referencing valid message keys.</em></p>

<p>Once this is done, we can visit the Spanish version of the app at <code>/es</code>.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="668"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png"
			
			sizes="100vw"
			alt="The Spanish version of the app"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/11-app-basic-es-framed.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>So far, so good!</p>

<div class="partners__lead-place"></div>

<h2 id="adding-interactivity-dynamic-ordering-of-photos">Adding Interactivity: Dynamic Ordering Of Photos</h2>

<p>By default, the Unsplash API returns the most popular photos. We want the user to be able to change the order to show the most recent photos first.</p>

<p>Here, the question arises whether we should resort to client-side data fetching so that we can implement this feature with <code>useState</code>. However, that would require us to move all of our components to the client side, resulting in an increased bundle size.</p>

<p>Do we have an alternative? Yes. And it’s a capability that has been around on the web for ages: <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams">search parameters</a> (sometimes referred to as <em>query parameters</em>). What makes search parameters a great option for our use case is that they can be read on the server side.</p>

<p>So let’s modify our page component to receive <code>searchParams</code> via props.</p>

<pre><code class="language-javascript">export default async function Index({searchParams}) {
  const orderBy = searchParams.orderBy || OrderBy.POPULAR;

  const [/&#42; ... &#42;/, photosRequest] = await Promise.all([
    /&#42; ... &#42;/,
    UnsplashApiClient.topics.getPhotos({orderBy, /&#42; ... &#42;/})
  ]);
</code></pre>

<p>After this change, the user can navigate to <code>/?orderBy=latest</code> to change the order of the displayed photos.</p>

<p>To make it easy for the user to change the value of the search parameter, we’d like to render an interactive <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"><code>select</code> element</a> from within a component.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="234"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png"
			
			sizes="100vw"
			alt="The app’s order select with the most popular photos displayed"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/5-app-order-select-collapsed.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We can mark the component with <code>'use client';</code> to attach an event handler and process change events from the <code>select</code> element. Nevertheless, we would like to keep the internationalization concerns on the server side to reduce the size of the client bundle.</p>

<p>Let’s have a look at the required markup for our <code>select</code> element.</p>

<pre><code class="language-html">&lt;select&gt;
  &lt;option value="popular"&gt;Popular&lt;/option&gt;
  &lt;option value="latest"&gt;Latest&lt;/option&gt;
&lt;/select&gt;
</code></pre>

<p>We can split this markup into two parts:</p>

<ol>
<li>Render the <code>select</code> element with an interactive Client Component.</li>
<li>Render the internationalized <code>option</code> elements with a Server Component and pass them as <code>children</code> to the <code>select</code> element.</li>
</ol>

<p>Let’s implement the <code>select</code> element for the client side.</p>

<pre><code class="language-javascript">'use client';

import {useRouter} from 'next-intl/client';

export default function OrderBySelect({orderBy, children}) {
  const router = useRouter();

  function onChange(event) {
    // The `useRouter` hook from `next-intl` automatically
    // considers a potential locale prefix of the pathname.
    router.replace('/?orderBy=' + event.target.value);
  }

  return (
    &lt;select defaultValue={orderBy} onChange={onChange}&gt;
      {children}
    &lt;/select&gt;
  );
}
</code></pre>

<p>Now, let’s use our component in <code>PhotoViewer</code> and provide the localized <code>option</code> elements as <code>children</code>.</p>

<pre><code class="language-javascript">import {useTranslations} from 'next-intl';
import OrderBySelect from './OrderBySelect';

export default function PhotoViewer({orderBy, /&#42; ... &#42;/}) {
  const t = useTranslations('PhotoViewer');

  return (
    &lt;&gt;
      {/&#42; ... &#42;/}
      &lt;OrderBySelect orderBy={orderBy}&gt;
        &lt;option value="popular"&gt;{t('orderBy.popular')}&lt;/option&gt;
        &lt;option value="latest"&gt;{t('orderBy.latest')}&lt;/option&gt;
      &lt;/OrderBySelect&gt;
    &lt;/&gt;
  );
}
</code></pre>

<p>With this pattern, the markup for the <code>option</code> elements is now generated on the server side and passed to the <code>OrderBySelect</code>, which handles the change event on the client side.</p>

<p><strong>Tip</strong>: <em>Since we have to wait for the updated markup to be generated on the server side when the order is changed, we may want to show the user a loading state. React 18 introduced <a href="https://beta.reactjs.org/reference/react/useTransition">the <code>useTransition</code> hook</a>, which is integrated with Server Components. This allows us to disable the <code>select</code> element while waiting for a response from the server.</em></p>

<pre><code class="language-javascript">import {useRouter} from 'next-intl/client';
import {useTransition} from 'react';

export default function OrderBySelect({orderBy, children}) {
  const [isTransitioning, startTransition] = useTransition();
  const router = useRouter();

  function onChange(event) {
    startTransition(() =&gt; {
      router.replace('/?orderBy=' + event.target.value);
    });
  }

  return (
    &lt;select disabled={isTransitioning} /&#42; ... &#42;/&gt;
      {children}
    &lt;/select&gt;
  );
}
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="adding-more-interactivity-page-controls">Adding More Interactivity: Page Controls</h2>

<p>The same pattern that we’ve explored for changing the order can be applied to page controls by introducing a <code>page</code> search parameter.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="146"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png"
			
			sizes="100vw"
			alt="App&#39;s pagination"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/6-app-pagination.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Note that languages have different rules for handling decimal and thousand separators. Furthermore, languages have different forms of pluralization: while English only makes a grammatical distinction between one and zero/many elements, for example, Croatian has a separate form for ‘few’ elements.</p>

<p><code>next-intl</code> uses the <a href="https://next-intl-docs.vercel.app/docs/usage/messages#rendering-of-messages">ICU syntax</a> which makes it possible to express these language subtleties.</p>

<div class="break-out">

<pre><code class="language-javascript">// en.json
{
  "Pagination": {
    "info": "Page {page, number} of {totalPages, number} ({totalElements, plural, =1 {one result} other {# results}} in total)",
    // ...
  }
}
</code></pre>
</div>

<p>This time we don’t need to mark a component with <code>'use client';</code>. Instead, we can implement this with regular anchor tags.</p>

<div class="break-out">

<pre><code class="language-javascript">import {ArrowLeftIcon, ArrowRightIcon} from '@heroicons/react/24/solid';
import {Link, useTranslations} from 'next-intl';

export default function Pagination({pageInfo, orderBy}) {
  const t = useTranslations('Pagination');
  const totalPages = Math.ceil(pageInfo.totalElements / pageInfo.size);

  function getHref(page) {
    return {
      // Since we're using `Link` from next-intl, a potential locale
      // prefix of the pathname is automatically considered.
      pathname: '/',
      // Keep a potentially existing `orderBy` parameter. 
      query: {orderBy, page}
    };
  }

  return (
    &lt;&gt;
      {pageInfo.page &gt; 1 && (
        &lt;Link aria-label={t('prev')} href={getHref(pageInfo.page - 1)}&gt;
          &lt;ArrowLeftIcon /&gt;
        &lt;/Link&gt;
      )}
      &lt;p&gt;{t('info', {...pageInfo, totalPages})}&lt;/p&gt;
      {pageInfo.page &lt; totalPages && (
        &lt;Link aria-label={t('prev')} href={getHref(pageInfo.page + 1)}&gt;
          &lt;ArrowRightIcon /&gt;
        &lt;/Link&gt;
      )}
    &lt;/&gt;
  );
}
</code></pre>
</div>

<h2 id="conclusion">Conclusion</h2>

<h3 id="server-components-are-a-great-match-for-internationalization">Server Components Are A Great Match For Internationalization</h3>

<p>Internationalization is an important part of the user experience, whether you support multiple languages or you want to get the subtleties of a single language right. A library like <a href="https://next-intl-docs.vercel.app/"><code>next-intl</code></a> can help with both cases.</p>

<p>Implementing internationalization in Next.js apps has historically come with a performance tradeoff, but with Server Components, this is no longer the case. However, it might take some time to explore and learn patterns that will help you keep your internationalization concerns on the server side.</p>

<p>In our street photography viewer app, we only needed to move a single component to the client side: <code>OrderBySelect</code>.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="515"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png"
			
			sizes="100vw"
			alt="App’s components"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/internationalization-nextjs-13-react-server-components/12-app-components.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Another aspect to note is that you might want to consider implementing loading states since the network latency introduces a delay before your users see the result of their actions.</p>

<h3 id="search-parameters-are-a-great-alternative-to-usestate">Search Parameters Are A Great Alternative To <code>useState</code></h3>

<p>Search parameters are a great way to implement interactive features in Next.js apps, as they help to reduce the bundle size of the client side.</p>

<p>Apart from performance, there are other <strong>benefits of using search parameters</strong>:</p>

<ul>
<li>URLs with search parameters can be shared while preserving the application state.</li>
<li>Bookmarks preserve the state as well.</li>
<li>You can optionally integrate with the browser history, enabling undoing state changes via the back button.</li>
</ul>

<p>Note, however, that there are also <strong>tradeoffs to consider</strong>:</p>

<ul>
<li>Search parameter values are strings, so you may need to serialize and deserialize data types.</li>
<li>The URL is part of the user interface, so using many search parameters may affect readability.</li>
</ul>

<p>You can have a look at the complete <a href="https://github.com/amannn/street-photography-viewer">code of the example on GitHub</a>.</p>

<p><em>Many thanks to <a href="https://twitter.com/delba_oliveira">Delba de Oliveira</a> from Vercel for providing feedback for this article!</em></p>

<h3 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2023/02/understanding-app-directory-architecture-next-js/">Understanding App Directory Architecture In Next.js</a>”, Atila Fassina</li>
<li>“<a href="https://www.smashingmagazine.com/2019/05/designing-users-across-cultures-interview-jenny-shen/">Designing For Users Across Cultures: An Interview With Jenny Shen</a>”, Rachel Andrew</li>
<li>“<a href="https://www.smashingmagazine.com/2022/04/dynamic-data-fetching-authenticated-nextjs-app/">Dynamic Data-Fetching In An Authenticated Next.js App</a>”, Caleb Olojo</li>
<li>“<a href="https://www.smashingmagazine.com/2021/10/search-functionality-nuxt-app-algolia-instantsearch/">How To Implement Search Functionality In Your Nuxt App Using Algolia InstantSearch</a>”, Miracle Onyenma</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Paul Scanlon</author><title>How To Create Dynamic Donut Charts With TailwindCSS And React</title><link>https://www.smashingmagazine.com/2023/03/dynamic-donut-charts-tailwind-css-react/</link><pubDate>Tue, 07 Mar 2023 15:30:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/03/dynamic-donut-charts-tailwind-css-react/</guid><description>In this article, Paul Scanlon shares a super lightweight approach to creating a Donut chart using &lt;code>conic-gradient()&lt;/code>. There are no additional libraries to install or maintain, and there’s no heavy JavaScript that needs to be downloaded by the browser in order for them to work. Let’s explore!</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/03/dynamic-donut-charts-tailwind-css-react/" />
              <title>How To Create Dynamic Donut Charts With TailwindCSS And React</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>How To Create Dynamic Donut Charts With TailwindCSS And React</h1>
                  
                    
                    <address>Paul Scanlon</address>
                  
                  <time datetime="2023-03-07T15:30:00&#43;00:00" class="op-published">2023-03-07T15:30:00+00:00</time>
                  <time datetime="2023-03-07T15:30:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>CSS is amazing &mdash; I’m regularly surprised at how far it has come in the years I’ve been using it (~2005 &ndash; present). One such surprise came when I noticed this tweet by <a href="https://twitter.com/shrutibalasa/status/1612785019159982080?s=20&amp;t=6TLkMmRjOFQxKP7W-jFPcA">Shruti Balasa</a> which demonstrated how to create a pie chart using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient#gradient_pie-chart"><code>conic-gradient()</code></a>.</p>

<p>It’s fairly straightforward. Here’s a code snippet:</p>

<pre><code class="language-css">div {
  background: conic-gradient(red 36deg, orange 36deg 170deg, yellow 170deg);
  border-radius: 50%;
}
</code></pre>

<p>Using this tiny amount of CSS, you can create gradients that start and stop at specific angles and define a color for each ‘segment’ of the pie chart.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg"
			
			sizes="100vw"
			alt="CSS conic-gradient charts with Donut Charts and a Pie Chart"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media//articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/1-css-conic-gradient-charts.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="happy-days">Happy Days!</h2>

<p>Brills, I thought I could use this instead of a charting library for a data dashboard project I’m working on for the new <a href="https://www.cockroachlabs.com/docs/api/cloud/v1.html#get-/api/v1/clusters">CockroachDB Cloud API</a>, but I had a problem. I didn’t know the values for my chart ahead of time, and the values I was receiving from the API weren’t in degrees!</p>

<p>Here’s a preview link and Open-source repo of how I worked around those two problems, and in the rest of this post, I’ll explain how it all works.</p>

<ul>
<li>🚀 Preview: <a href="https://css-conic-gradient-charts.vercel.app/">https://css-conic-gradient-charts.vercel.app/</a></li>
<li>⚙️ Repo: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts">https://github.com/PaulieScanlon/css-conic-gradient-charts</a></li>
</ul>

<h2 id="dynamic-data-values">Dynamic Data Values</h2>

<p>Here’s some sample data from a <em>typical</em> API response which I’ve sorted by <code>value</code>.</p>

<pre><code class="language-css">const data = [
  {
    name: 'Cluster 1',
    value: 210,
  },
  {
    name: 'Cluster 2',
    value: 30,
  },
  {
    name: 'Cluster 3',
    value: 180,
  },
  {
    name: 'Cluster 4',
    value: 260,
  },
  {
    name: 'Cluster 5',
    value: 60,
  },
].sort((a, b) =&gt; a.value - b.value);
</code></pre>

<p>You can see that each item in the array has a <code>name</code> and a <code>value</code>.</p>

<p>In order to convert the <code>value</code> from a number into a <code>deg</code> value to use with CSS, there are a few things you need to do:</p>

<ul>
<li>Calculate the total amount of all the values.</li>
<li>Use the total amount to calculate the percentage that each value represents.</li>
<li>Convert the percentage into degrees.</li>
</ul>

<p><strong>Note</strong>: <em>The code I’ll be referring to in the steps below can be found in the repo here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-1.js">/components/donut-1.js</a>.</em></p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h3 id="calculate-the-total-amount">Calculate The Total Amount</h3>

<p>Using JavaScript, you can use this little one-liner to <em>sum</em> up each value from the data array, which results in a single total.</p>

<pre><code class="language-javascript">const total&#95;value = data.reduce((a, b) =&gt; a + b.value, 0);

// =&gt; 740
</code></pre>

<h3 id="calculate-the-percentage">Calculate The Percentage</h3>

<p>Now that you have a <code>total_value</code>, you can convert each of the values from the data array to a percentage using a JavaScript function. I’ve called this function <code>covertToPercent</code>.</p>

<p><strong>Note</strong>: <em>I’ve used the value of 210 from Cluster 1 in this example.</em></p>

<pre><code class="language-javascript">const convertToPercent = (num) =&gt; Math.round((num / total&#95;value) &#42; 100);

// convertToPercent(210) =&gt; 28
</code></pre>

<h3 id="convert-percentage-to-degrees">Convert Percentage to Degrees</h3>

<p>Once you have a percentage, you can convert the percentage into degrees using another JavaScript function. I’ve called this function <code>convertToDegrees</code>.</p>

<pre><code class="language-javascript">const convertToDegrees = (num) =&gt; Math.round((num / 100) &#42; 360);

// convertToDegrees(28) =&gt; 101
</code></pre>

<h3 id="the-result">The Result</h3>

<p>As a temporary test, if I were to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a> over the items in the sorted data array, using the two functions explained above, you’d end up with the following output:</p>

<pre><code class="language-javascript">const test_output = data.map((item) =&gt; {
  const percentage = convertToPercent(item.value);
  const degrees = convertToDegrees(percentage);

  return `${degrees}deg`;
});

// =&gt; ['14deg', '29deg', '86deg', '101deg', '126deg']
</code></pre>

<p>The return value of <code>test_output</code> is an array of the <code>value</code> (in degrees) + the string <code>deg</code>.</p>

<p>This solves one of a two-part problem. I’ll now explain the other part of the problem.</p>

<p>To create a Pie chart using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient#gradient_pie-chart"><code>conic-gradient()</code></a>, you need two <code>deg</code> values. The first is the angle from where the gradient should start, and the second is the angle where the gradient should stop. You’ll also need a color for each segment, but I’ll come to that in a moment.</p>

<div class="break-out">

<pre><code class="language-css"> ['red 🤷 14deg', 'blue 🤷 29deg', 'green 🤷 86deg', 'orange 🤷 101deg', 'pink 🤷 126deg']
</code></pre>
</div>

<p>Using the values from the <code>test_output</code>, I only have the end value (where the gradient should stop). The start angle for each segment is actually the end angle from the previous item in the array, and the end angle is the cumulative value of all previous end values plus the current end value. And to make matters worse, the start value for the first angle needs to be manually set to <code>0</code> 🥴.</p>

<p>Here’s a diagram to better explain what that means:</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png"
			
			sizes="100vw"
			alt="A diagram which explains a function"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/3-diagram-pie-chart-conic-gradient.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If that sounds confusing, it’s because it is, but if you look at the output of a function that can do all this, it might make more sense.</p>

<pre><code class="language-css">"#...", 0, 14,
"#...",, 14, 43,
"#...",, 43, 130,
"#...",, 130, 234,
"#...",, 234, 360,
</code></pre>

<h2 id="the-function-that-can-do-all-this">The Function That Can Do All This</h2>

<p>And here’s the function that can indeed do all of this. It uses <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"><code>reduce()</code></a> to iterate over the data array, performs the necessary addition to calculate the angles, and returns a new set of numbers that can be used to create the correct start and end angles for use in a Chart.</p>

<div class="break-out">

<pre><code class="language-javascript">const total&#95;value = data.reduce((a, b) =&gt; a + b.value, 0);
const convertToPercent = (num) =&gt; Math.round((num / total&#95;value) &#42; 100);
const convertToDegrees = (num) =&gt; Math.round((num / 100) &#42; 360);

const css&#95;string = data
  .reduce((items, item, index, array) =&gt; {
    items.push(item);

    item.count = item.count || 0;
    item.count += array[index - 1]?.count || item.count;
    item.start&#95;value = array[index - 1]?.count ? array[index - 1].count : 0;
    item.end&#95;value = item.count += item.value;
    item.start&#95;percent = convertToPercent(item.start&#95;value);
    item.end&#95;percent = convertToPercent(item.end&#95;value);
    item.start&#95;degrees = convertToDegrees(item.start&#95;percent);
    item.end&#95;degrees = convertToDegrees(item.end&#95;percent);

    return items;
  }, [])
  .map((chart) =&gt; {
    const { color, start&#95;degrees, end&#95;degrees } = chart;
    return ` ${color} ${start&#95;degrees}deg ${end&#95;degrees}deg`;
  })
  .join();
</code></pre>
</div>

<p>I’ve purposefully left this pretty verbose, so it’s easier to add in <code>console.log()</code>. I found this to be quite helpful when I was developing this function.</p>

<p>You might notice the additional <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"><code>map</code></a> chained to the end of the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"><code>reduce</code></a>. By using a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"><code>map</code></a> I’m able to modify the returned values and tack on <code>deg</code>, then return them all together as an array of strings.</p>

<p>Using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join"><code>join</code></a> right at the end converts the array back to a single <code>css_string</code>, which can be used with <code>conic-gradient()</code> 😅.</p>

<pre><code class="language-css">"#..." 0deg 14deg,
"#..." 14deg 43deg,
"#..." 43deg 130deg,
"#..." 130deg 234deg,
"#..." 234deg 360deg
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="using-the-css-string-with-an-svg-foreignobject">Using The <code>css_string</code> With An SVG <code>foreignObject</code></h2>

<p>Now, unfortunately, you can’t use <code>conic-gradient()</code> with <a href="https://developer.mozilla.org/en-US/docs/Web/SVG">SVG</a>. But you can wrap an HTML element inside a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject"><code>foreignObject</code></a> and style the <code>background</code> using a <code>conic-gradient()</code>.</p>

<div class="break-out">

<pre><code class="language-html">&lt;svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' style={{ borderRadius: '100%' }}&gt;
  &lt;foreignObject x='0' y='0' width='100' height='100'&gt;
    &lt;div
      xmlns='http://www.w3.org/1999/xhtml'
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`, // &lt;- 🥳
      }}
    /&gt;
  &lt;/foreignObject&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>Using the above, you should be looking at a Pie chart. In order to make a Donut chart, I’ll need to explain how to make the hole.</p>

<h2 id="let-s-talk-about-the-hole">Let’s Talk About the Hole</h2>

<p>There’s only really one way you can ‘mask’ off the middle of the Pie chart to reveal the background. This approach involves using a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath"><code>clipPath</code></a>. This approach looks like the below code snippet. I’ve used this for Donut 1.</p>

<p><strong>Note</strong>: <em>The <code>src</code> for Donut 1 can be seen here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-1.js#L44">components/donut-1.js</a>.</em></p>

<div class="break-out">

<pre><code class="language-html">&lt;svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' style={{ borderRadius: '100%' }}&gt;

  &lt;clipPath id='hole'&gt;
    &lt;path d='M 50 0 a 50 50 0 0 1 0 100 50 50 0 0 1 0 -100 v 18 a 2 2 0 0 0 0 64 2 2 0 0 0 0 -64' /&gt;
  &lt;/clipPath&gt;

  &lt;foreignObject x='0' y='0' width='100' height='100' clipPath='url(#hole)'&gt;
    &lt;div
      xmlns='http://www.w3.org/1999/xhtml'
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`
      }}
    /&gt;
  &lt;/foreignObject&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>However, there is another way. This approach involves using a <code>&lt;circle /&gt;</code> element and placing it in the center of the pie chart. This will work if the fill of the <code>&lt;circle /&gt;</code> matches the background color of whatever the chart is placed on. In my example, I’ve used a pattern background, and you’ll notice if you look closely at Donut 3 that you can’t see the <a href="https://heropatterns.com/">bubble pattern</a> through the center of the chart.</p>

<p><strong>Note</strong>: <em>The <code>src</code> for Donut 3 can be seen here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-3.js#L44">components/donut-3.js</a>.</em></p>

<div class="break-out">

<pre><code class="language-html">&lt;svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' style={{ borderRadius: '100%' }}&gt;
  &lt;foreignObject x='0' y='0' width='100' height='100'&gt;
    &lt;div
      xmlns='http://www.w3.org/1999/xhtml'
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css&#95;string})`
      }}
    /&gt;
  &lt;/foreignObject&gt;
  &lt;circle cx='50' cy='50' r='32' fill='white' /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>IMO the <code>clipPath</code> approach is nicer, but it can be more difficult to amend the path points to get the desired thickness of the hole if you don’t have access to something like Figma or Illustrator.</p>

<h2 id="finally-colors">Finally, Colors!</h2>

<p>Colors for charts are something that always cause me problems. Most of the time, the colors I use are defined in CSS, and all this stuff is happening in JavaScript, so how do you use CSS variables in JavaScript?</p>

<p>In my example site, I’m using <a href="https://tailwindcss.com/">Tailwind</a> to style ‘all the things’ and by using <a href="https://gist.github.com/Merott/d2a19b32db07565e94f10d13d11a8574">this trick</a>, I’m able to expose the CSS variables so they can be referred to by their name.</p>

<p>If you want to do the same, you could add a <code>color</code> key to the data array:</p>

<pre><code class="language-css">data={[
  {
    name: 'Cluster 1',
    value: 210,
    color: 'var(--color-fuchsia-400)',
  },
  {
    name: 'Cluster 2',
    value: 30,
    color: 'var(--color-fuchsia-100)',
  },
  {
    name: 'Cluster 3',
    value: 180,
    color: 'var(--color-fuchsia-300)',
  },
  {
    name: 'Cluster 4',
    value: 260,
    color: 'var(--color-fuchsia-500)',
  },
  {
    name: 'Cluster 5',
    value: 60,
    color: 'var(--color-fuchsia-200)',
  },
].sort((a, b) =&gt; a.value - b.value)
</code></pre>

<p>And then reference the <code>color</code> key in the array <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"><code>map</code></a> to return it as part of the <code>css_string</code>. I’ve used this approach in Donut 2.</p>

<p><strong>Note</strong>: <em>You can see the <code>src</code> for Donut 2 here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-2.js#L25">components/donut-2.js</a>.</em></p>

<pre><code class="language-javascript">.map((chart) =&gt; {
  const { color, start&#95;degrees, end&#95;degrees } = chart;
  return ` ${color} ${start&#95;degrees}deg ${end&#95;degrees}deg`;
})
.join();
</code></pre>

<p>You could even dynamically create the color name using a hard-coded value (<code>color-pink-</code>) + the <code>index</code> from the array. I’ve used this approach in Donut 1.</p>

<p><strong>Note</strong>: <em>You can see the <code>src</code> for Donut 1 here: <a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/donut-1.js#L26">components/donut-1.js</a>.</em></p>

<div class="break-out">

<pre><code class="language-javascript">.map((chart, index) =&gt; {
  const { start&#95;degrees, end&#95;degrees } = chart;
  return ` var(--color-pink-${(index + 1) &#42; 100}) ${start&#95;degrees}deg ${end&#95;degrees}deg`;
})
.join();
</code></pre>
</div>

<div class="partners__lead-place"></div>

<h2 id="if-you-re-lucky">If You’re Lucky!</h2>

<p>However, you might get lucky and be working with an API that actually returns values with an associated color. This is the case with the <a href="https://docs.github.com/en/graphql">GitHub GraphQL API</a>. So. I popped together one last example.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png"
			
			sizes="100vw"
			alt="GitHub GraphQL API with Github chart with ten different languages associated with its own color"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/dynamic-donut-charts-css-conic-gradient-tailwindcss-react/2-ccs-conic-gradient-charts.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can see this working in your browser by visiting <a href="https://css-conic-gradient-charts.vercel.app/github">/github</a>, and the <code>src</code> for both the GitHub Donut Chart and Legend can be found here:</p>

<ul>
<li><a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/github-chart.js">components/github-chart.js</a>;</li>
<li><a href="https://github.com/PaulieScanlon/css-conic-gradient-charts/blob/main/components/github-legend.js">components/github-legend.js</a>.</li>
</ul>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>You might be thinking this is quite complicated, and it’s probably easier to use a Charting Library, and you’re probably right. It probably is. But this way is <strong>super lightweight</strong>. There are no additional libraries to install or maintain, and there’s no heavy JavaScript that needs to be downloaded by the browser in order for them to work.</p>

<p>I experimented once before with creating Donut Charts using an SVG and the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray"><code>stroke-dashoffset</code></a>. You can read about that in my article, “<a href="https://paulie.dev/posts/2021/01/react-svg-doughnut-chart/">Create an SVG Doughnut Chart From Scratch For Your Gatsby Blog</a>.” That approach worked really well, but I think I prefer the approach described in this post. CSS is simply the best!</p>

<p>If you’d like to discuss any of the methods I’ve used here, please come find me on Twitter: <a href="https://twitter.com/PaulieScanlon">@PaulieScanlon</a>.</p>

<p>See you around the internet!</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Nefe Emadamerho-Atori</author><title>Comparing React Form Libraries: SurveyJS, Formik, React Hook Form, React Final Form And Unform</title><link>https://www.smashingmagazine.com/2023/02/comparing-react-form-libraries/</link><pubDate>Thu, 02 Feb 2023 12:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/02/comparing-react-form-libraries/</guid><description>In handling forms in React, you can either set up a custom solution or reach out to one of the many form libraries available. In this article, we compare some React Libraries: SurveyJS, Formik, React Hook Form, React Final Form, and Unform.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/02/comparing-react-form-libraries/" />
              <title>Comparing React Form Libraries: SurveyJS, Formik, React Hook Form, React Final Form And Unform</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Comparing React Form Libraries: SurveyJS, Formik, React Hook Form, React Final Form And Unform</h1>
                  
                    
                    <address>Nefe Emadamerho-Atori</address>
                  
                  <time datetime="2023-02-02T12:00:00&#43;00:00" class="op-published">2023-02-02T12:00:00+00:00</time>
                  <time datetime="2023-02-02T12:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>SurveyJS</b></p>
                

<p>Working with user input has always been one of the most vital parts of developing any website. Handling things like form validation, submission, and displaying errors can become complex, so using existing form solutions may be the way to go, and there are several solutions for dealing with forms in React.</p>

<p>In this article, we will look at <a href="#react-form-library-by-surveyjs">SurveyJS</a>, <a href="#formik">Formik</a>, <a href="#react-hook-form">React Hook Form</a>, <a href="#react-final-form">React Final Form</a> and <a href="#unform">Unform</a>. We will compare how they are used, how we can integrate them into custom UI components, and how to set up dependent fields with them. Along the way, we will also learn how to validate forms using <a href="https://github.com/jquense/yup">Yup</a>, a JavaScript object schema validator.</p>

<p>This article is useful for those who want to know the best form libraries to use for future applications.</p>

<p><strong><em>Note:</em></strong> <em>This article requires a basic understanding of React and Yup.</em></p>

<h2 id="should-you-use-a-form-library">Should You Use A Form Library?</h2>

<p>Forms are an integral part of how users interact with the web. They are used to collect data for processing from users, and many websites today have one or more forms. While forms can be handled in React by making them <a href="https://reactjs.org/docs/forms.html">controlled components</a>, it can become tedious with a lot of repetitive code if you build a lot of forms. You have an option of reaching out to one of the many form libraries that exist in the React ecosystem. These libraries make it easier to build forms of varying complexity, as they provide validation and state management out of the box, among other useful form-handling features.</p>

<h2 id="factors-to-be-considered">Factors To Be Considered</h2>

<p>It is one thing to know the different form libraries available, and another to know the appropriate one to use for your next project. In this article, we will examine how these form libraries work and compare them based on the following factors:</p>

<ul>
<li><strong>Implementation</strong><br />
We will look at their APIs, and consider how easy it is to integrate them into an app, handle form validation, submission, and the overall developer experience.</li>
<li><strong>Usage with Custom Components</strong><br />
How easy is it to integrate these libraries with inputs from UI libraries like Material UI?</li>
<li><strong>Dependent Fields</strong><br />
You may want to render a form field B that depends on the value of a field A. How can we handle that use case with these libraries?</li>
<li><strong>Learning Curve</strong><br />
How quickly can you start using these forms? How much learning resources and examples are available online?<br /></li>
<li><strong>Bundle Size</strong><br />
We always want our applications to be performant. What tradeoffs are there in terms of the bundle size of these forms?</li>
</ul>

<p>We will also consider how to make <strong>multi step forms</strong> using these libraries. I didn’t add that to the list above due to how I structured the article. We will look at that at the end of the article.</p>

<h2 id="react-form-library-by-surveyjs">React Form Library by SurveyJS</h2>

<p>SurveyJS Form Library is an <a href="https://github.com/surveyjs/survey-library">open-source</a> client-side component that renders dynamic JSON-driven forms in React applications. It uses JSON objects to communicate with the server. These objects, also known as JSON schemas, define various aspects of a form, including its style, contents, layout, and behavior in response to user interactions, such as data submission, input validation, error messages, and so on.</p>

<p>The library has native support for React. It is free to use and is distributed under the MIT license. The <a href="https://surveyjs.io/">SurveyJS product family</a> also includes a self-hosted JSON form builder that features drag-and-drop UI, a CSS Theme Editor, and a GUI for conditional logic and form branching.</p>

<h3 id="features">Features</h3>

<ul>
<li>It&rsquo;s suitable for multi-page forms, quizzes, scored surveys, calculator forms, and survey pop-ups.</li>
<li><a href="https://surveyjs.io/documentation/backend-integration">Compatible with any server &amp; database</a>.</li>
<li><a href="https://surveyjs.io/backend-integration/examples">Integration demos for PHP, ASP.NET Core, and NodeJS</a>.</li>
<li><a href="https://surveyjs.io/form-library/documentation/how-to-store-survey-results">All data is stored on your own servers</a>; therefore, there are no limits on the number of forms, submissions, and file uploads.</li>
<li>20+ accessible input types, panels for question grouping, dynamic questions with a duplicate group option.</li>
<li><a href="https://surveyjs.io/form-library/documentation/data-validation">Input validation</a>, partial submits &amp; auto-save, lazy loading, load choices from web services.</li>
<li>Custom input fields</li>
<li>Carry forward responses, text piping, autocomplete</li>
<li>Integration with 3rd-party libraries and payment systems</li>
<li>Support for webhooks</li>
<li>Expression language (Built-in &amp; Custom Functions), data aggregation within a form</li>
<li><a href="https://surveyjs.io/form-library/documentation/survey-localization">Auto-localization and multi-locale surveys</a>, support for RTL languages</li>
<li><a href="https://surveyjs.io/stay-updated/release-notes">Weekly updates</a></li>
<li><a href="https://surveyjs.io/form-library/examples/overview">120+ starter demos &amp; tutorials</a></li>
</ul>

<h3 id="how-to-install">How To Install</h3>

<pre><code class="language-npm">npm install survey-react-ui --save
</code></pre>

<h3 id="how-to-use">How To Use</h3>

<div class="break-out">
<pre><code class="language-javascript">import 'survey-core/defaultV2.min.css';
import { Model } from 'survey-core';
import { Survey } from 'survey-react-ui';

const surveyJson = {
  elements: [{
    name: "FirstName",
    title: "Enter your first name:",
    type: "text"
  }, {
    name: "LastName",
    title: "Enter your last name:",
    type: "text"
  }]
};

function App() {
  const survey = new Model(surveyJson);

  return &lt;Survey model={survey} /&gt;;
}

export default App;
</code></pre>
</div>

<p>SurveyJS Form Library for React consists of two npm packages: <a href="https://www.npmjs.com/package/survey-core"><code>survey-core</code></a> (platform-independent code) and <a href="https://www.npmjs.com/package/survey-react-ui"><code>survey-react-ui</code></a> (rendering code). Run the <code>npm install survey-react-ui --save</code> command to install <code>survey-react-ui</code>. The survey-core package will be installed automatically as a dependency. Another advantage of SurveyJS is its seamless integration with custom UI libraries and their form components. This <a href="https://surveyjs.io/form-library/documentation/customize-question-types/third-party-component-integration-react">dedicated guide</a> demonstrates how to integrate the <a href="https://casesandberg.github.io/react-color/">React Color</a> component to a basic SurveyJS form.</p>

<p>To add SurveyJS themes to your application, open the React component that will render a form and import the Form Library style sheet <code>import 'survey-core/defaultV2.min.css';</code>. This style sheet applies the Default theme. You can also apply a different <a href="https://surveyjs.io/form-library/documentation/manage-default-themes-and-styles">predefined theme or create a custom one</a>.</p>

<p>Next, you need to create a model that describes the layout and contents of a form. Models are specified by model schemas (JSON objects). SurveyJS website offers a <a href="https://surveyjs.io/create-free-survey">full-featured JSON form builder demo</a> that you can use to generate JSON schemas for your forms. In this example, model schema declares two <a href="https://surveyjs.io/form-library/documentation/api-reference/text-entry-question-model">textual questions</a>, each with a <a href="https://surveyjs.io/form-library/documentation/api-reference/text-entry-question-model#title">title</a> and a <a href="https://surveyjs.io/form-library/documentation/api-reference/text-entry-question-model#name">name</a>. Titles are visible to respondents, while names are used to identify the questions in code. To instantiate a model, pass the model schema to the <a href="https://surveyjs.io/form-library/documentation/api-reference/survey-data-model">Model</a> constructor as shown in the code above.</p>

<p>To render a form, import the <code>Survey</code> component, add it to the template, and pass the model instance you created in the previous step to the component&rsquo;s <code>model</code> attribute.</p>

<p>As a result, you should see the following form:</p>

<figure class="break-out">
  <iframe width="100%" height="530" frameborder="0" scrolling="no" seamless="true" style="width:100%; height:530px;" src="https://react-cc4rdv.stackblitz.io/"></iframe>
    <figcaption><a href="https://github.com/surveyjs/code-examples/tree/main/get-started-library/react">View Full Source Code on GitHub</a></figcaption>
</figure>

<h2 id="formik">Formik</h2>

<p><a href="https://formik.org/docs/overview">Formik</a> is a flexible library. You can choose to use Formik with native HTML elements or with Formik’s custom components. You also have the option of setting up your form validation rules or a third-party solution like Yup. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use.</p>

<p>Formik takes care of the repetitive and annoying stuff &mdash; keeping track of values, errors, visited fields, orchestrating validation, and handling submission &mdash; so you don&rsquo;t have to. This means you spend less time setting up form state and <code>onChange</code> and <code>onBlur</code> handlers.</p>

<h3 id="installation">Installation</h3>

<pre><code class="language-bash">npm i formik
yarn add formik</code></pre>

<h3 id="implementation">Implementation</h3>

<p>Formik keeps track of your form&rsquo;s state and then exposes it plus a few reusable methods and event handlers (<code>handleChange</code>, <code>handleBlur</code>, and <code>handleSubmit</code>) to your form via <code>props</code>. You can find out more about the methods available in Formik <a href="https://formik.org/docs/api/formik#props">here</a>.</p>

<p>While Formik can be used alongside HTML’s native input fields, Formik comes with a <a href="https://formik.org/docs/api/field"><code>Field</code></a> component that you can use to determine the input field you want, and an <a href="https://formik.org/docs/api/errormessage"><code>ErrorMessage</code></a> component that handles displaying the error for each input field. Let’s see how these work in practice.</p>

<iframe src="https://codesandbox.io/embed/formik-codesandbox-demo-u1dux?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Formik Codesandbox Demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<div class="break-out">
<pre><code class="language-javascript">import { Form, Field, ErrorMessage, withFormik } from "formik";

const App = ({ values }) =&gt; (
  &lt;div className={styles.container}&gt;
    &lt;Head&gt;
      &lt;title&gt;Formik Form&lt;/title&gt;
    &lt;/Head&gt;
    &lt;Form&gt;
      &lt;div className={styles.formRow}&gt;
        &lt;label htmlFor="email"&gt;Email&lt;/label&gt;
        &lt;Field type="email" name="email" id="email" /&gt;
        &lt;ErrorMessage name="email" component="span" className={styles.error} /&gt;
      &lt;/div&gt;
      &lt;div className={styles.formRow}&gt;
        &lt;label htmlFor="email"&gt;Select a color to continue&lt;/label&gt;
        &lt;Field component="select" name="select"&gt;
          &lt;option value="" label="Select a color" /&gt;
          &lt;option value="red" label="red" /&gt;
          &lt;option value="blue" label="blue" /&gt;
          &lt;option value="green" label="green" /&gt;
        &lt;/Field&gt;
        &lt;ErrorMessage name="select" component="span" className={styles.error} /&gt;
      &lt;/div&gt;
      &lt;div className={styles.formRow}&gt;
        &lt;label htmlFor="checkbox"&gt;
          &lt;Field type="checkbox" name="checkbox" checked={values.checkbox} /&gt;
          Accept Terms & Conditions
        &lt;/label&gt;
        &lt;ErrorMessage
          name="checkbox"
          component="span"
          className={styles.error}
        /&gt;
      &lt;/div&gt;
      &lt;div role="group" aria-labelledby="my-radio-group"&gt;
        &lt;label&gt;
          &lt;Field type="radio" name="radio" value="Option 1" /&gt;
          One
        &lt;/label&gt;
        &lt;label&gt;
          &lt;Field type="radio" name="radio" value="Option 2" /&gt;
          Two
        &lt;/label&gt;
        &lt;ErrorMessage name="radio" component="span" className={styles.error} /&gt;
      &lt;/div&gt;
      &lt;button type="submit" className={"disabled-btn"}&gt;
        Sign In
      &lt;/button&gt;
    &lt;/Form&gt;
  &lt;/div&gt;
);</code></pre>
</div>

<p>In the code above, we are working with four input fields, an email, a select, a checkbox, and a radio field. <a href="https://formik.org/docs/api/form"><code>Form</code></a> is a small wrapper around an HTML <code>&lt;form&gt;</code> element that automatically hooks into Formik&rsquo;s <code>handleSubmit</code> and <code>handleReset</code>. We will look into what <code>withFormik</code> does next.</p>

<div class="break-out">
<pre><code class="language-javascript">import { Form, Field, ErrorMessage, withFormik } from "formik";
import * as Yup from "yup";

const App = ({ values }) =&gt; (
  &lt;div className={styles.container}&gt;
    &lt;Head&gt;
      &lt;title&gt;Formik Form&lt;/title&gt;
    &lt;/Head&gt;
    &lt;Form&gt;
      //form stuffs here
      &lt;button type="submit" className={"disabled-btn"}&gt;
        Sign In
      &lt;/button&gt;
    &lt;/Form&gt;
  &lt;/div&gt;
);

const FormikApp = withFormik({
  mapPropsToValues: ({ email, select, checkbox, radio }) =&gt; {
    return {
      email: email || "",
      select: select || "",
      checkbox: checkbox || false,
      radio: radio || "",
    };
  },
  validationSchema: Yup.object().shape({
    select: Yup.string().required("Color is required!"),
    email: Yup.string().email().required("Email is required"),
    checkbox: Yup.bool().oneOf([true], "Checkbox is required"),
    radio: Yup.string().required("Radio is required!"),
  }),
  handleSubmit: (values) =&gt; {
    alert(JSON.stringify(values));
  },
})(App);

export default FormikApp;</code></pre>
</div>

<p><a href="https://formik.org/docs/api/withFormik"><code>withFormik</code></a> is a <a href="https://www.smashingmagazine.com/2020/06/higher-order-components-react/">HOC</a> that injects Formik context within the wrapped component. We can pass an <code>options</code> object into <code>withFormik</code> where we define the behaviour of the Formik context.</p>

<p>Formik works well with Yup in handling the form validation, so we don’t have to set up custom validation rules. We define a schema for validation pass it to <code>validationSchema</code>. With <a href="https://formik.org/docs/api/withFormik#mappropstovalues-props-props--values"><code>mapPropsToValues</code></a>, Formik transfers the updated state of the input fields and makes the values available to the <code>App</code> component through props as <code>props.values</code>. The <a href="https://formik.org/docs/api/withFormik#handlesubmit-values-values-formikbag-formikbag--void--promiseany"><code>handleSubmit</code></a> function handles the form submission.</p>

<h3 id="usage-with-custom-components">Usage With Custom Components</h3>

<p>Another benefit of Formik is how straightforward it is to integrate custom UI libraries and their form components. Here, we set up a basic form using Material UI’s <a href="https://material-ui.com/api/text-field/"><code>TextField</code></a> component.</p>

<div class="break-out">
<pre><code class="language-javascript">import TextField from "@material-ui/core/TextField";
import * as Yup from "yup";
import { Formik, Form, Field, ErrorMessage } from "formik";

const signInSchema = Yup.object().shape({
  email: Yup.string().email().required("Email is required"),
});

export default function SignIn() {
  const classes = useStyles();
  return (
    &lt;Container component="main" maxWidth="xs"&gt;
      &lt;div className={classes.paper}&gt;
        &lt;Formik
          initialValues={initialValues}
          validationSchema={SignInSchema}
          onSubmit={(values) =&gt; {
            alert(JSON.stringify(values));
          }}
        &gt;
          {({
            errors,
            values,
            handleChange,
            handleBlur,
            handleSubmit,
            touched,
          }) =&gt; (
            &lt;Form className={classes.form} onSubmit={handleSubmit}&gt;
              &lt;Field
                as={TextField}
                variant="outlined"
                margin="normal"
                fullWidth
                id="email"
                label="Email Address"
                name="email"
                helperText={&lt;ErrorMessage name="email" /&gt;}
              /&gt;
              &lt;button type="submit"&gt;submit&lt;/button&gt;
            &lt;/Form&gt;
          )}
        &lt;/Formik&gt;
      &lt;/div&gt;
    &lt;/Container&gt;
  );
}</code></pre>
</div>

<p><code>Field</code> hooks up inputs to Formik automatically. Formik injects <code>onChange</code>, <code>onBlur</code>, <code>name</code>, and <code>value</code> props to the <code>TextField</code> component. It does the same for any type of custom component you decide to use. We pass <code>TextField</code> to <code>Field</code> through it’s <code>as</code> prop.</p>

<p>We can also use Material UI’s <code>TextField</code> component directly. The docs also provide <a href="https://formik.org/docs/examples/with-material-ui">an example</a> that covers that scenario.</p>

<h3 id="dependent-fields">Dependent Fields</h3>

<p>To set up dependent fields in Formik, we access the input’s value we want to track through the <code>values</code> object in the render props. Here, we are tracking the <code>remember</code> field, which is the checkbox, and rendering a message based on the state of the field.</p>

<pre><code class="language-javascript"> &lt;Field
    name="remember"
    type="checkbox"
    as={Checkbox}
    Label={{ label: "You must accept our terms!!!" }}
      helperText={&lt;ErrorMessage name="remember" /&gt;}
    /&gt;
    
{values.remember && (
  &lt;p&gt;
    Thank you for accepting our terms. You can now submit the
    form
  &lt;/p&gt;
)}</code></pre>

<h3 id="learning-curve">Learning Curve</h3>

<p>Formik’s <a href="https://formik.org/docs/overview">docs</a> are easy to understand and straight to the point. It covers several use cases, including how to use Formik with third-party UI libraries like Material UI. There are also several <a href="https://formik.org/docs/resources">resources</a> from the Formik community to aid your learning.</p>

<h3 id="bundle-size">Bundle Size</h3>

<p>Formik is 44.4kb minified and 13.1kb gzipped.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="309"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png"
			
			sizes="100vw"
			alt="Formik bundle size"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Formik bundle size. (<a href='https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="react-hook-form">React Hook Form</h2>

<p><a href="https://react-hook-form.com/get-started">React Hook Form</a>, or RHF is a lightweight, zero-dependency, and flexible form library built for React.</p>

<h3 id="installation-1">Installation</h3>

<pre><code class="language-bash">npm i react-hook-form
yarn add react-hook-form</code></pre>

<h3 id="implementation-1">Implementation</h3>

<p>RHF provides a <a href="https://www.react-hook-form.com/api/useform"><code>useForm</code></a> hook which we can use to work with forms.</p>

<iframe src="https://codesandbox.io/embed/react-hook-form-codesandbox-demo-p8lk8?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="React Hook Form Codesandbox Demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<p>We start by setting up the HTML input fields we need for this form. Unlike Formik, RHF does not have a custom <code>Field</code> component, so we will use HTML’s native input fields.</p>

<div class="break-out">
<pre><code class="language-javascript">import { useForm } from "react-hook-form";

const validationSchema = Yup.object().shape({
  select: Yup.string().required("Color is required!"),
  email: Yup.string().email().required("Email is required"),
  checkbox: Yup.bool().oneOf([true], "Checkbox is required"),
  radio: Yup.string().required("Radio is required!"),
});

const onSubmit = (values) =&gt; {
  alert(JSON.stringify(values));
};

const App = () =&gt; {
  const { errors, register, handleSubmit } = useForm({
    resolver: yupResolver(validationSchema),
  });
  return (
    &lt;div className="container"&gt;
      &lt;form onSubmit={handleSubmit(onSubmit)}&gt;
        //email field
        &lt;div className="form-row"&gt;
          &lt;label htmlFor="email"&gt;Email&lt;/label&gt;
          &lt;input type="email" name="email" id="email" ref={register} /&gt;
          {errors.email && &lt;p className="error"&gt; {errors.email.message} &lt;/p&gt;}
        &lt;/div&gt;

        //select field
        &lt;div className="form-row"&gt;
          &lt;label htmlFor="email"&gt;Select a color to continue&lt;/label&gt;
          &lt;select name="select" ref={register}&gt;
            &lt;option value="" label="Select a color" /&gt;
            &lt;option value="red" label="red" /&gt;
            &lt;option value="blue" label="blue" /&gt;
            &lt;option value="green" label="green" /&gt;
          &lt;/select&gt;
          {errors.select && &lt;p className="error"&gt; {errors.select.message} &lt;/p&gt;}
        &lt;/div&gt;

        //checkbox field
        &lt;div className="form-row"&gt;
          &lt;label htmlFor="checkbox"&gt;
            &lt;input type="checkbox" name="checkbox" ref={register} /&gt;
            Accept Terms & Conditions
          &lt;/label&gt;
          {errors.checkbox && (
            &lt;p className="error"&gt; {errors.checkbox.message} &lt;/p&gt;
          )}
        &lt;/div&gt;

        //radio field
        &lt;div&gt;
          &lt;label&gt;
            &lt;input type="radio" name="radio" value="Option 1" ref={register} /&gt;
            One
          &lt;/label&gt;
          &lt;label&gt;
            &lt;input type="radio" name="radio" value="Option 2" ref={register} /&gt;
            Two
          &lt;/label&gt;
          {errors.radio && &lt;p className="error"&gt; {errors.radio.message} &lt;/p&gt;}
        &lt;/div&gt;
        &lt;button type="submit"&gt;Sign In&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};</code></pre>
</div>

<p>RHF supports Yup and other <a href="https://react-hook-form.com/get-started#SchemaValidation">validation schemas</a>. To use Yup with RHF, we need to install the <code>@hookform/resolvers</code> package.</p>

<pre><code class="language-bash">npm i @hookform/resolvers</code></pre>

<p>Next, we have to configure the RHF setup and instruct it to use Yup as the form validator. We do so through the <code>resolver</code> property  <code>useForm</code> hook’s configuration. object. We pass in <code>yupResolver</code>, and now RHF knows to use Yup to validate the form.</p>

<p>The <code>useForm</code> hook gives us access to several form methods and properties like an <a href="https://react-hook-form.com/v6/api#errors"><code>errors</code></a> object, and the <a href="https://react-hook-form.com/v6/api#handleSubmit"><code>handleSubmit</code></a> and <a href="https://react-hook-form.com/v6/api#register"><code>register</code></a> methods. There are other methods we can extract from <code>useForm</code>. You can find <a href="https://react-hook-form.com/v6/api#useForm">the complete list of methods</a>.</p>

<p>The <code>register</code> function connects input fields to RHF through the input field’s <code>ref</code> prop. We pass the <code>register</code> function as a <code>ref</code> into each element we want RHF to watch. This approach makes the forms more performant and avoids unnecessary re-renders.</p>

<p>The <code>handleSubmit</code> method handles the form submission. It will only run if there are no errors in the form.</p>

<p>The <code>errors</code> object contains the errors present in each field.</p>

<h3 id="usage-with-custom-components-1">Usage With Custom Components</h3>

<p>RHF has made it easy to integrate with external UI component libraries. When using custom components, check if the component you wish to use exposes a <code>ref</code>. If it does, you can use it like you would native HTML form elements. However, if it doesn’t you will need to use RHF’s <a href="https://react-hook-form.com/v6/api#Controller"><code>Controller</code></a> component.</p>

<p>Material-UI and Reactstrap&rsquo;s <code>TextField</code> expose their <code>inputRef</code>, so you can pass <code>register</code> to it.</p>

<pre><code class="language-javascript">import TextField from "@material-ui/core/TextField";

export default function SignIn() {
  return (
    &lt;Container component="main" maxWidth="xs"&gt;
      &lt;div className={classes.paper}&gt;
        &lt;form className={classes.form}&gt;
          &lt;TextField
            inputRef={register}
            id="email"
            label="Email Address"
            name="email"
            error={!!errors.email}
            helperText={errors?.email?.message}
          /&gt;
          &lt;Button type="submit"&gt;Sign In&lt;/Button&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    &lt;/Container&gt;
  );
}</code></pre>

<p>In a situation where the custom component’s <code>inputRef</code> is not exposed, we have to use RHF’s <code>Controller</code> component.</p>

<div class="break-out">
<pre><code class="language-javascript">import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";

export default function SignIn() {
  const { control } = useForm()

  return (
    &lt;Container component="main" maxWidth="xs"&gt;
      &lt;div className={classes.paper}&gt;
        &lt;form&gt;
          &lt;FormControlLabel
            control={
              &lt;Controller
                control={control}
                name="remember"
                color="primary"
                render={(props) =&gt; (
                  &lt;Checkbox
                    checked={props.value}
                    onChange={(e) =&gt; props.onChange(e.target.checked)}
                  /&gt;
                )}
              /&gt;
            }
            label="Remember me"
          /&gt;
          &lt;Button type="submit"&gt; Sign In &lt;/Button&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    &lt;/Container&gt;
  );
}</code></pre>
</div>

<p>We import the <code>Controller</code> component from RHF and access the <code>control</code> object from the <code>useForm</code> hook.</p>

<p><code>Controller</code> acts as a wrapper that allows us to use custom components in RHF. Any prop passed into <code>Controller</code> will be propagated down to the <code>Checkbox</code>.</p>

<p>The <code>render</code> prop function returns a React element and provides the ability to attach events and value into the component. This simplifies integrating RHF with custom components. <code>render</code> provides <code>onChange</code>, <code>onBlur</code>, <code>name</code>, <code>ref</code>, and <code>value</code> to the custom component.</p>

<h3 id="dependent-fields-1">Dependent Fields</h3>

<p>In some situations, you may want to render a secondary form field based on the value a user puts in field a primary form field. RHF provides a <a href="https://react-hook-form.com/v6/api#watch"><code>watch</code></a> <a href="https://www.react-hook-form.com/api/useform/watch/">API</a> that enables us to track the value of am input field.</p>

<div class="break-out">
<pre><code class="language-javascript">export default function SignIn() {
  const { watch } = useForm()
  const terms = watch("remember");

  return (
    &lt;Container component="main" maxWidth="xs"&gt;
      &lt;div className={classes.paper}&gt;
        &lt;form&gt;
            //other fields above
          {terms && &lt;p&gt;Thank you for accepting our terms. You can now submit the form&lt;/p&gt;}
          &lt;Button type="submit"&gt; Sign In &lt;/Button&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    &lt;/Container&gt;
  );
}</code></pre>
</div>

<h3 id="learning-curve-1">Learning Curve</h3>

<p>Asides from its extensive and straightforward <a href="https://react-hook-form.com/api">documentation</a> that covers several use cases, RHF is a very popular React Library. This means there are several <a href="https://react-hook-form.com/resources">learning resources</a> to get you up and running.</p>

<h3 id="bundle-size-1">Bundle Size</h3>

<p>RHF is 26.4kb minified and 9.1kb gzipped.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="333"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png"
			
			sizes="100vw"
			alt="React Hook Form bundle size"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      React Hook Form bundle size. (<a href='https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="final-form">Final Form</h2>

<p>Final Form is a framework-agnostic form library. However, its creator, Erik Rasmussen, created a React wrapper for Final Form, <a href="https://final-form.org/docs/react-final-form/getting-started">React Final Form</a>.</p>

<h3 id="installation-2">Installation</h3>

<pre><code class="language-bash">npm i final-form react-final-form
yarn add final-form react-final-form</code></pre>

<h3 id="implementation-2">Implementation</h3>

<p>Unlike Formik and React Hook Form, React Final Form (RFF) does not support validation with Object Schemas like Yup out of the box. This means you have to set up validation yourself.</p>

<iframe src="https://codesandbox.io/embed/react-final-form-codesandbox-demo-ng8rs?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="React Final Form Codesandbox Demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<div class="break-out">
<pre><code class="language-javascript">import { Form, Field } from "react-final-form";

export default function App() {
  return (
    &lt;div className={styles.container}&gt;
      &lt;Head&gt;
        &lt;title&gt;React Final Form&lt;/title&gt;
      &lt;/Head&gt;
      &lt;Form
        onSubmit={onSubmit}
        validate={validate}
        render={({ handleSubmit }) =&gt; (
          &lt;form onSubmit={handleSubmit}&gt;

            //email field
            &lt;Field name="email"&gt;
              {({ input, meta }) =&gt; (
                &lt;div className={styles.formRow}&gt;
                  &lt;label&gt;Email&lt;/label&gt;
                  &lt;input {...input} type="email" placeholder="Email" /&gt;
                &lt;/div&gt;
              )}
            &lt;/Field&gt;

            //select field
            &lt;Field name="select" component="select"&gt;
              {({ input, meta }) =&gt; (
                &lt;div className={styles.formRow}&gt;
                  &lt;label htmlFor="select"&gt;Select a color to continue&lt;/label&gt;
                  &lt;select {...input}&gt;
                    &lt;option value="" label="Select a color" /&gt;
                    &lt;option value="red" label="red" /&gt;
                    &lt;option value="blue" label="blue" /&gt;
                    &lt;option value="green" label="green" /&gt;
                  &lt;/select&gt;
                &lt;/div&gt;
              )}
            &lt;/Field&gt;

            //checkbox field
            &lt;Field name="checkbox"&gt;
              {({ input, meta }) =&gt; (
                &lt;div className={styles.formRow}&gt;
                  &lt;label&gt;
                    &lt;input {...input} name="checkbox" type="checkbox" /&gt;
                    Accept Terms & Conditions
                  &lt;/label&gt;
                &lt;/div&gt;
              )}
            &lt;/Field&gt;

            //radio field
            &lt;div className={styles.formRow}&gt;
2              &lt;Field
                name="radio"
                component="input"
                type="radio"
                value="Option 1"
              &gt;
                {({ input, meta }) =&gt; (
                  &lt;div&gt;
                    &lt;label&gt;
                      One
                      &lt;input {...input} type="radio" value="Option 1" /&gt;
                    &lt;/label&gt;
                  &lt;/div&gt;
                )}
              &lt;/Field&gt;
              &lt;Field
                name="radio"
                component="input"
                type="radio"
                value="Option 2"
              &gt;
                {({ input, meta }) =&gt; (
                  &lt;div&gt;
                    &lt;label&gt;
                      Two
                      &lt;input {...input} type="radio" value="Option 2" /&gt;
                    &lt;/label&gt;
                  &lt;/div&gt;
                )}
              &lt;/Field&gt;
            &lt;/div&gt;
            &lt;button type="submit" className={"disabled-btn"}&gt;
              Sign In
            &lt;/button&gt;
          &lt;/form&gt;
        )}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
</div>

<p>The <a href="https://final-form.org/docs/react-final-form/api/Form"><code>Form</code></a> component is a special wrapper provided by RFF that manages the state of the form. The main <code>props</code> when using RFF are <code>onSubmit</code>, <code>validate</code>, and <code>render</code>. You can get more details on the <a href="https://final-form.org/docs/react-final-form/types/FormProps">form props</a> RFF works with.</p>

<p>We start by setting up the necessary input fields. <code>render</code> handles the rendering of the form. Through the render props, we have access to the <a href="https://final-form.org/docs/final-form/types/FormState"><code>FormState</code></a> object. It contains form methods like <code>handleSubmit</code>, and other useful properties regarding the state of the form.</p>

<p>Like Formik, RFF has its own <a href="https://final-form.org/docs/react-final-form/api/Field"><code>Field</code></a> component for rendering input fields. The <code>Field</code> component registers any input field in it, subscribes to the input field’s state, and injects both field state and callback functions, <code>onBlur</code>, <code>onChange</code>, and <code>onFocus</code> via a render prop.</p>

<p>Unlike Formik and RHF, RFF does not provide support for any validation Schema, so we have to set up custom validation rules.</p>

<div class="break-out">
<pre><code class="language-javascript">const onSubmit = (values) =&gt; {
  alert(JSON.stringify(values));
};

const validate = (values) =&gt; {
  const errors = {};
  if (!values.email) {
    errors.email = "Email is Required";
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
    errors.email = "Invalid emaill address";
  }
  if (!values.checkbox) {
    errors.checkbox = "You must accept our terms";
  }
  if (!values.select) {
    errors.select = "Select is required";
  }
  if (!values.radio) {
    errors.radio = "You must accept our terms";
  }
  return errors;
};

export default function App() {
  return (
    &lt;div className={styles.container}&gt;
      &lt;Form
        onSubmit={onSubmit}
        validate={validate}
        render={({ handleSubmit }) =&gt; (
          &lt;form onSubmit={handleSubmit}&gt;
            &lt;Field name="email"&gt;
              {({ input, meta }) =&gt; (
                &lt;div className={styles.formRow}&gt;
                  &lt;label&gt;Email&lt;/label&gt;
                  &lt;input {...input} type="email" placeholder="Email" /&gt;
                  {meta.error && meta.touched && (
                    &lt;span className={styles.error}&gt;{meta.error}&lt;/span&gt;
                  )}
                &lt;/div&gt;
              )}
            &lt;/Field&gt;
            &lt;Field name="select" component="select"&gt;
              {({ input, meta }) =&gt; (
                &lt;div className={styles.formRow}&gt;
                  &lt;label htmlFor="select"&gt;Select a color to continue&lt;/label&gt;
                  &lt;select {...input}&gt;
                    &lt;option value="" label="Select a color" /&gt;
                    &lt;option value="red" label="red" /&gt;
                    &lt;option value="blue" label="blue" /&gt;
                    &lt;option value="green" label="green" /&gt;
                  &lt;/select&gt;
                  {meta.error && meta.touched && (
                    &lt;span className={styles.error} style={{ display: "block" }}&gt;
                      {meta.error}
                    &lt;/span&gt;
                  )}
                &lt;/div&gt;
              )}
            &lt;/Field&gt;
            &lt;Field name="checkbox"&gt;
              {({ input, meta }) =&gt; (
                &lt;div className={styles.formRow}&gt;
                  &lt;label&gt;
                    &lt;input {...input} name="checkbox" type="checkbox" /&gt;
                    Accept Terms & Conditions
                  &lt;/label&gt;
                  {meta.error && meta.touched && (
                    &lt;span className={styles.error}&gt;{meta.error}&lt;/span&gt;
                  )}
                &lt;/div&gt;
              )}
            &lt;/Field&gt;
            &lt;div className={styles.formRow}&gt;
              &lt;Field
                name="radio"
                component="input"
                type="radio"
                value="Option 1"
              &gt;
                {({ input, meta }) =&gt; (
                  &lt;div&gt;
                    &lt;label&gt;
                      One
                      &lt;input {...input} type="radio" value="Option 1" /&gt;
                    &lt;/label&gt;
                  &lt;/div&gt;
                )}
              &lt;/Field&gt;
              &lt;Field
                name="radio"
                component="input"
                type="radio"
                value="Option 2"
              &gt;
                {({ input, meta }) =&gt; (
                  &lt;div&gt;
                    &lt;label&gt;
                      Two
                      &lt;input {...input} type="radio" value="Option 2" /&gt;
                    &lt;/label&gt;
                    {meta.error && meta.touched && (
                      &lt;span className={styles.error}&gt;{meta.error}&lt;/span&gt;
                    )}
                  &lt;/div&gt;
                )}
              &lt;/Field&gt;
            &lt;/div&gt; 
            &lt;button type="submit" className={"disabled-btn"}&gt;
              Sign In
            &lt;/button&gt;
          &lt;/form&gt;
        )}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
</div>

<p>The <code>validate</code> function handles the validation for the form. The <code>onSubmit</code> function will be called with the values of your form when the user submits the form and all validation passes. Validation runs on input change by default. However, we can also pass a <a href="https://final-form.org/docs/react-final-form/types/FormProps#validateonblur"><code>validateonBlur</code></a> prop to the <code>Form</code> component validation so it also runs on blur.</p>

<p>To display the validation errors, we make use of the <code>meta</code> object. We can get access to the metadata and state of each input field through the <code>meta</code> object. We can find out if the form has been touched or has any errors through the <code>meta</code>&rsquo;s <code>touched</code> and <code>error</code> properties respectively. These metadata are part of the <a href="https://final-form.org/docs/react-final-form/types/FieldRenderProps#metatouched">props of the</a> <a href="https://final-form.org/docs/react-final-form/types/FieldRenderProps#metaactive"><code>Field</code></a> <a href="https://final-form.org/docs/react-final-form/types/FieldRenderProps#metaactive">component</a>. If the input fields have been touched, and there is an error for we display that error.</p>

<h3 id="usage-with-custom-components-2">Usage With Custom Components</h3>

<p>Working with custom input components in RFF is straightforward. Using the render props method in the <code>Field</code> component, we can access the <code>input</code> and <code>meta</code> of each input field.</p>

<div class="break-out">
<pre><code class="language-javascript">const onSubmit = (values) =&gt; {...};
const validate = (values) =&gt; {...};
export default function App() { 
  return (
      &lt;Form
        onSubmit={onSubmit}
        validate={validate}
        render={({ handleSubmit }) =&gt; (
          &lt;form onSubmit={handleSubmit}&gt;
            &lt;Field name="email" placeholder="email" validate={validate}&gt;
              {({ input, meta }) =&gt; (
                &lt;div&gt;
                  &lt;TextField label="email" type="email" {...input} /&gt;
                  {meta.error && meta.touched && &lt;span&gt;{meta.error}&lt;/span&gt;}
                &lt;/div&gt;
              )}
            &lt;/Field&gt;
            &lt;Button type="submit"&gt; Sign In &lt;/Button&gt;
          &lt;/form&gt;
        )}
      &gt;&lt;/Form&gt;
    );
  }
}</code></pre>
</div>

<p>RFF’s <code>Field</code> component bundles all of the props that your input component needs into one object prop, called <code>input</code>, which contains <code>name</code>, <code>onBlur</code>, <code>onChange</code>, <code>onFocus</code>, and <code>value</code>. The <code>input</code> prop is what we spread to the <code>TextField</code> component. The custom form component you plan on using must support these props in order to be compatible with RFF.</p>

<p>Alternatively, we could render Material UI’s <code>TextField</code> using the component Field in RFF. However, we won’t be able to access the <code>input</code>  and <code>meta</code> data using this method.</p>

<pre><code class="language-javascript">///other form stuff above
&lt;Field
  name="email"
  component={TextField}
  type="email"
  label="Email"
/&gt;

//we won’t be able to access the input 
//and meta data using this method.

///other form stuff below</code></pre>

<h3 id="dependent-fields-2">Dependent Fields</h3>

<p>The <code>values</code> object can be accessed from the render prop. From here, we can track the state of the <code>remember</code> field, and if <code>true</code>, render a message, or whatever use case fits your app’s needs.</p>

<div class="break-out">
<pre><code class="language-javascript">&lt;Form
    onSubmit={onSubmit}
    render={({ handleSubmit, values }) =&gt; (
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;Field name="remember" type="checkbox"&gt;
          {({ input }) =&gt; (
            &lt;div&gt;
              &lt;label&gt;Remember me&lt;/label&gt;
              &lt;input {...input} type="checkbox" /&gt;
            &lt;/div&gt;
          )}
        &lt;/Field&gt;
        {values.remember && (
          &lt;p&gt;Thank you for accepting our terms. You can now submit the form.&lt;/p&gt;
        )}
        &lt;button type="submit"&gt; Submit&lt;/button&gt;
      &lt;/form&gt;
    )}
  /&gt;</code></pre>
</div>

<h3 id="learning-curve-2">Learning Curve</h3>

<p>Compared to other form libraries, RFF does not have as many learning resources. Also, the <a href="https://final-form.org/docs/react-final-form/getting-started">docs</a> do not go into detail to show how RFF can be used with Yup or any other validation schema, and that would be a helpful addition.</p>

<h3 id="bundle-size-2">Bundle Size</h3>

<p>RFF is 8.9kb minified and 3.2kb gzipped.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="331"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png"
			
			sizes="100vw"
			alt="React Final Form bundle size"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      React Final Form bundle size. (<a href='https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="unform">Unform</h2>

<p>A core aspect of Unform’s API design is how straightforward it is to hook form inputs to <a href="https://unform.dev/">Unform</a>. <a href="https://www.npmjs.com/package/@rocketseat/unform">Initially</a>, Unform came with its built-in input components, however, <a href="https://unform.dev/migration-guide/#input-components">it no longer</a> <a href="https://unform.dev/migration-guide/#input-components">follows that pattern</a>. This means you have to register each field you want Unform to track. We can do that with the <code>registerField</code> method Unform provides.</p>

<iframe src="https://codesandbox.io/embed/unform-codesandbox-demo-j0pcv?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Unform Codesandbox Demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<h3 id="installation-3">Installation</h3>

<pre><code class="language-bash">npm i @unform/web @unform/core
yarn add @unform/web @unform/core</code></pre>

<h3 id="implementation-3">Implementation</h3>

<p>The first step in using Unform is registering the input fields we want the library to track.</p>

<p>The <a href="https://unform.dev/api/use-field"><code>useField</code></a> hook is the heart of Unform. From this hook, we get access to the <code>fieldName</code>, <code>defaultValue</code>, <code>registerField</code>, <code>error</code>, and more. Let’s see what they do and how they work.</p>

<pre><code class="language-javascript">import { useEffect, useRef } from "react";
import { useField } from "@unform/core";

const Input = ({ name, label, ...rest }) =&gt; {
  const inputRef = useRef();
   const {
    fieldName,
    defaultValue,
    registerField,
    error,
    clearError
  } = useField(name);  

  useEffect(() =&gt; {
    registerField({
      name: fieldName,
      ref: inputRef.current,
      getValue: (ref) =&gt; {
        return ref.value;
      }
    });
  }, [fieldName, registerField]);
  return (
    &lt;&gt;
      &lt;label htmlFor={fieldName}&gt;{label}&lt;/label&gt;
      &lt;input
        id={fieldName}
        ref={inputRef}
        onFocus={clearError}
        defaultValue={defaultValue}
        {...rest}
      /&gt;
      {error && &lt;span className="error"&gt;{error}&lt;/span&gt;}
    &lt;/&gt;
  );
};
export default Input;</code></pre>

<ul>
<li><code>fieldName</code>: a unique field name.</li>
<li><code>defaultValue</code>: the default value of the field.</li>
<li><code>registerField</code>: this method is used to register a field on Unform. When registering a field, you can pass some properties to the <code>registerField</code> method.</li>
<li><code>error</code>: the error message of the registered input field.</li>
</ul>

<p>Whenever the input component loads, we call the <code>registerField</code> method in the <code>useEffect</code> to register the input. <code>registerField</code> accepts some options:</p>

<ul>
<li><code>name</code>: the name of the field that needs to be registered.</li>
<li><code>ref</code>: the reference to the field.</li>
<li><code>getValue</code>: this function returns the value of the field.</li>
</ul>

<p>We pass the methods; <code>fieldName</code>, <code>defaultValue</code>, <code>clearError</code> and any other props to the input component. <code>clearError</code> clears the error of an input field on focus if there is any. This is how we register input fields with Unform. We do the same thing for the select, checkbox, and radio input fields.</p>

<p>Now that we have registered the input fields, we have to bring everything together.</p>

<div class="break-out">
<pre><code class="language-javascript">import React, { useRef } from "react";
import { Form } from "@unform/web";
import * as Yup from "yup";
import Input from "./Input";
import Radio from "./Radio";
import Checkbox from "./Checkbox";
import Select from "./Select";

const radioOptions = [
  { value: "option 1", label: "One" },
  { value: "option 2", label: "Two" }
];
const selectOptions = [
  { value: "", label: "Select a color" },
  { value: "red", label: "Red" },
  { value: "blue", label: "Blue" },
  { value: "green", label: "Green" }
];

const App = () =&gt; {
  const formRef = useRef();
  async function handleSubmit(data) {
   //form validation goes here  
  }
  return (
    &lt;div className="container"&gt;
      &lt;Form ref={formRef} onSubmit={handleSubmit}&gt;
        &lt;div className="form-row"&gt;
          &lt;Input name="email" label="Email" type="email" /&gt;
        &lt;/div&gt;
        &lt;div className="form-row"&gt;
          &lt;Select
            name="select"
            label="Select a color to continue"
            options={selectOptions}
          /&gt;
        &lt;/div&gt;
        &lt;div className="form-row"&gt;
          &lt;Checkbox name="checkbox" label="Accept terms and conditions" /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;Radio name="radio" options={radioOptions} /&gt;
        &lt;/div&gt;
        &lt;button className="button" type="submit"&gt;
          Sign in
        &lt;/button&gt;
      &lt;/Form&gt;
    &lt;/div&gt;
  );
};
export default App;</code></pre>
</div>

<p>We import the <code>Form</code> component from Unform and set up a form reference for the <code>Form</code> component. We get access to several <a href="https://unform.dev/recipes/accessing-form-ref#methods">helpful methods</a> from this reference.</p>

<p>Now that we’ve registered the input fields, created a form reference, and set up the form, the next step is to handle the form validation and submission.</p>

<pre><code class="language-javascript">import React, { useRef } from "react";

const validationSchema = Yup.object().shape({
  select: Yup.string().required("Color is required!"),
  email: Yup.string().email().required("Email is required"),
  checkbox: Yup.bool().oneOf([true], "Checkbox is required"),
  radio: Yup.string().required("Radio is required!")
});

const App = () =&gt; {
  const formRef = useRef();
  async function handleSubmit(data) {
    try {
      await validationSchema.validate(data, {
        abortEarly: false
      });
      // Validation passed - do something with data
      alert(JSON.stringify(data));
    } catch (err) {
      const errors = {};
      // Validation failed - do show error
      err.inner.forEach((error) =&gt; {
        errors[error.path] = error.message;
      });
      formRef.current.setErrors(errors);
    }
  }

  return (
    &lt;div className="container"&gt;
      &lt;Form ref={formRef} onSubmit={handleSubmit}&gt;
    //other form stuffs below
}</code></pre>

<p>Unform supports validation with Yup, so we create a <code>schema</code>. By default, the schema’s <code>validate(</code>) method will reject the promise as soon as it finds the error and won’t validate any further fields. So to avoid that you need to pass the <code>abortEarly</code> option and set the boolean to false <code>{ abortEarly: false }</code>. We use the <code>setErrors</code> method from the form reference we created to set the errors for the form if any.</p>

<p>Similar to the <code>handleSubmit</code> function that handles submit validation, a <code>handleChange</code> function can be created that will handle form validation as the user types.</p>

<div class="break-out">
<pre><code class="language-javascript">import { useRef, useState } from "react";

export default function App() {
  const [form, setForm] = useState({
    name: ""
  });

  const formRef = useRef(null);

  async function handleSubmit(data) {
    //handleSubmit logic
  }
  const handleNameChange = async ({ target: { value } }) =&gt; {
    setForm({
      ...form,
      name: value
    });
    try {
      formRef.current.setErrors({});
      let schema = Yup.object().shape({
        name: Yup.string()
          .required()
          .max(20)
          .matches(
            /^[a-zA-Z]+(([',. -][a-zA-Z ])?[a-zA-Z]*)*$/,
            "Please enter valid name"
          )
      });
      await schema.validate(form, {
        abortEarly: false
      });
      console.log(form);
    } catch (err) {
      console.log(err);
      const validationErrors = {};
      if (err instanceof Yup.ValidationError) {
        err.inner.forEach((error) =&gt; {
          validationErrors[error.path] = error.message;
        });
        formRef.current.setErrors(validationErrors);
      }
    }
  };
  return (
    &lt;div className="container"&gt;
      &lt;Form ref={formRef} noValidate onSubmit={handleSubmit}&gt;
        &lt;div className="form-row"&gt;
          &lt;Input name="name" label="Full name" onChange={handleNameChange} /&gt;
        &lt;/div&gt;
        &lt;button type="submit" className="button"&gt;
          Save
        &lt;/button&gt;
      &lt;/Form&gt;
    &lt;/div&gt;
  );
}</code></pre>
</div>

<p>Both validation functions work the same way, so the logic for the submit and <code>onChange</code> validation are the same. However, there are some differences. Unlike validation on submit, where we can get access to and validate the form data with <code>handleSubmit</code>, we need a way to handle the input’s data in <code>handleNameChange</code>. To do that, we set up a form state where we will store the input’s value. Then as the user types, we update the form state through <code>setForm</code>. Now that we have access to the form data, we validate it in the same manner we did in <code>handleSubmit</code>. Lastly, we have to pass <code>handleNameChange</code> to the input’s <code>onChange</code> prop, which we do.</p>

<p>You will note that in <code>handleNameChange</code>, I created a different validation schema than the one I used in <code>handleSubmit</code>. I did this so there will be two different errors to make the <code>onChange</code> and <code>onSubmit</code> error different. However, in a real-world project, you would use the same validation schema.</p>

<p>The sandbox below provides a demo for validation on input change.</p>

<iframe src="https://codesandbox.io/embed/unform-onchange-validation-demo-f8k3r?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Unform OnChange Validation Demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<h3 id="usage-with-custom-components-3">Usage with Custom Components</h3>

<p>We integrate third-party UI components with Unform the same way we do with native HTML inputs, through the <code>registerField</code> method.</p>

<div class="break-out">
<pre><code class="language-javascript">import TextField from "@material-ui/core/TextField";

const MaterialUIInput = ({ name, label, ...rest }) =&gt; {
  const inputRef = useRef();
  const { fieldName, defaultValue, registerField, error } = useField(name);
  useEffect(() =&gt; {
    //form registration here
  }, [fieldName, registerField]);
  return (
    &lt;&gt;
      &lt;TextField
        inputRef={inputRef}
      /&gt;</code></pre>
</div>

<h3 id="dependent-fields-3">Dependent Fields</h3>

<p>Unform currently does not provide any watch functionality to help in creating <a href="https://github.com/unform/unform/issues/313">dependent fields</a>. However, there are plans in the library’s <a href="https://www.notion.so/Unform-public-roadmap-e4dff9e2053c4475b162cd19914eab02">roadmap</a> to create a <code>useWatch</code> hook for tracking the form state.</p>

<h3 id="learning-curve-3">Learning Curve</h3>

<p>Unform is not the easiest library to get started with. The process of registering input fields is not developer-friendly compared to other articles. Also, validation and accessing <code>errors</code> object in Unform is more tedious than it should be. It also doesn’t help that there is no way to access form values to set up dependent fields. It doesn’t come with a lot of features that would be needed when working with forms, and there are very few resources out there that show different use cases in using Unform. However, the documentation provides a few <a href="https://unform.dev/examples/react-select">examples</a>.</p>

<p>Overall, it is not the most straightforward library to use. I believe more work can be done in providing a better documentation.</p>

<p>With that being said, Unform is still under development, and the team is currently working on <a href="https://www.notion.so/Unform-public-roadmap-e4dff9e2053c4475b162cd19914eab02">new features</a>.</p>

<h3 id="bundle-size-3">Bundle Size</h3>

<p>Unform is 10.4kb minified and 3.7kb gzipped.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="333"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png"
			
			sizes="100vw"
			alt="Unform bundle size"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Unform bundle size. (<a href='https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="creating-multi-step-forms">Creating Multi Step Forms</h2>

<p>I have created multi-step form demos for each library. I left this to the last section because the implementation for the libraries remains similar. The only different thing is the multi-step form UI. Let’s see the structure of the UI.</p>

<h3 id="breaking-down-the-form-wizard">Breaking Down The Form Wizard</h3>

<p>For the multi step wizard, let’s see the file structure and how it works.</p>

<pre><code class="language-bash">├── components
|  ├── FormCard.js
|  ├── FormCompleted.js
|  └── Forms
|     ├── BillingInfo.js
|     ├── ConfirmPurchase.js
|     ├── index.js
|     └── PersonalInfo.js
├── context
|  └── index.js
├── pages
|  ├── api
|  |  └── hello.js
|  ├── index.js
|  └── _app.js
└── styles
   ├── globals.css
   └── styles.module.scss
</code></pre>

<p>Let’s look at the <code>App.js</code> file.</p>

<div class="break-out">
<pre><code class="language-javascript">const App = () =&gt; {
  const [formStep, setFormStep] = useState(0);
  const nextFormStep = () =&gt; setFormStep((currentStep) =&gt; currentStep + 1);
  const prevFormStep = () =&gt; setFormStep((currentStep) =&gt; currentStep - 1);
  return (
    &lt;div className={styles.container}&gt;
      &lt;Head&gt;
        &lt;title&gt;Next.js Multi Step Form&lt;/title&gt;
      &lt;/Head&gt;
      &lt;h1&gt;Formik Multi Step Form&lt;/h1&gt;
      &lt;FormCard currentStep={formStep} prevFormStep={prevFormStep}&gt;
        {formStep &gt;= 0 && (
          &lt;PersonalInfo formStep={formStep} nextFormStep={nextFormStep} /&gt;
        )}
        {formStep &gt;= 1 && (
          &lt;BillingInfo formStep={formStep} nextFormStep={nextFormStep} /&gt;
        )}
        {formStep &gt;= 2 && (
          &lt;ConfirmPurchase formStep={formStep} nextFormStep={nextFormStep} /&gt;
        )}
        {formStep &gt; 2 && &lt;FormCompleted /&gt;}
      &lt;/FormCard&gt;
    &lt;/div&gt;
  );
};</code></pre>
</div>

<p>Here, we define a <code>formStep</code> state, which holds the state of the current step of the form wizard. We also define <code>prevFormStep</code> and <code>nextFormStep</code> functions to go back and forth in the form wizard.</p>

<p>Next, we pass the <code>formStep</code> state <code>prevFormStep</code> to the <code>FormCard</code>. This will enable us to go a step backward and also display the current step of the form.</p>

<p>Finally, we pass <code>formStep</code> and <code>nextFormStep</code> to the form components. We conditionally render each form based on the value of <code>formStep</code>. We use <code>&gt;=</code> to render the forms because we want the forms to remain rendered even though it’s step has been passed. This makes tracking the form values easier.</p>

<p>Now the <code>FormCard</code> component. This is the container for each form.</p>

<div class="break-out">
<pre><code class="language-javascript">export default function FormCard({ children, currentStep, prevFormStep }) {
  return (
    &lt;div className={styles.formCard}&gt;
      {currentStep &lt; 3 && (
        &lt;&gt;
          {currentStep &gt; 0 && (
            &lt;button
              className={styles.back}
              onClick={prevFormStep}
              type="button"
            &gt;
              back
            &lt;/button&gt;
          )}
          &lt;span className={styles.steps}&gt;Step {currentStep + 1} of 3&lt;/span&gt;
        &lt;/&gt;
      )}
      {children}
    &lt;/div&gt;
  );
}</code></pre>
</div>

<p><code>FormCard</code> does 3 things: conditionally render the back button based on the value of <code>formStep</code>, show the value of the current step to the user as a form of progress tracker, and render its children, which is the current form being displayed.</p>

<p>The form components have a similar structure. Let’s see <code>PersonalInfo</code>.</p>

<div class="break-out">
<pre><code class="language-javascript">import { useFormData } from "../../context";

export default function PersonalInfo({ formStep, nextFormStep }) {
  const { setFormValues } = useFormData();
  const handleSubmit = (values) =&gt; {
    setFormValues(values);
    nextFormStep();
  };
  return (
    &lt;div className={formStep === 0 ? styles.showForm : styles.hideForm}&gt;
      &lt;h2&gt;Personal Info&lt;/h2&gt;
      &lt;form&gt;
        &lt;div className={styles.formRow}&gt;
          &lt;label htmlFor="email"&gt;Email&lt;/label&gt;
          &lt;input type="email" name="email" id="email" /&gt;
        &lt;/div&gt;
        &lt;button type="button" onClick={nextFormStep}&gt;
          Next
        &lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
</div>

<p>In the root <code>div</code>, we conditionally apply <code>showForm</code> and <code>hideForm</code> styles to show or hide the form. We do this because of how we implemented the form wizard.</p>

<div class="break-out">
<pre><code class="language-javascript">&lt;FormCard currentStep={formStep} prevFormStep={prevFormStep}&gt;
        {formStep >= 0 && (
          &lt;PersonalInfo formStep={formStep} nextFormStep={nextFormStep} /&gt;
        )}
       //other forms below</code></pre>
</div>

<p>We display a particular form based on the value of <code>formStep</code>. However, we use the <code>&gt;=</code> conditional to conditionally render a form. This means that all the forms will render as <code>formStep</code> increases. We want them to render, but also be hidden. That’s why we conditionally apply the <code>showForm</code> and <code>hideForm</code> styles.</p>

<p>Finally, we have the <code>handleSumbit</code>. Let’s look into how that works.</p>

<p>Handling form submission starts with creating a context to store the values of the forms.</p>

<pre><code class="language-javascript">import { useState, createContext, useContext } from "react";
export const FormContext = createContext();

export default function FormProvider({ children }) {
  const [data, setData] = useState({});
  const setFormValues = (values) =&gt; {
    setData((prevValues) =&gt; ({
      ...prevValues,
      ...values,
    }));
  };
  return (
    &lt;FormContext.Provider value={{ data, setFormValues }}&gt;
      {children}
    &lt;/FormContext.Provider&gt;
  );
}

export const useFormData = () =&gt; useContext(FormContext);</code></pre>

<p><code>setFormValues</code> is a function that takes the data from each form and uses those values to update the state of <code>data</code>, which will hold the values of each form.</p>

<p>We can access the form data and the <code>setFormValues</code> function from <code>useFormData</code>.</p>

<div class="break-out">
<pre><code class="language-javascript">import { useFormData } from "../../context";

export default function PersonalInfo({ formStep, nextFormStep }) {
  const { setFormValues } = useFormData();
  const handleSubmit = (values) =&gt; {
    setFormValues(values);
    nextFormStep();
  };</code></pre>
</div>

<p>In each form, we pull <code>setFormValues</code> from <code>useFormData</code> and pass in the values of the form. This way, as we submit each form, the data is being stored.</p>

<p>Finally, if the form has been successfully filled, the <code>FormCompleted</code> component renders.</p>

<pre><code class="language-javascript">export default function FormCompleted() {
  return &lt;h2&gt;Thank you for your purchase! 🎉&lt;/h2&gt;;
}</code></pre>

<p>The core aspect of creating the multi-step form is the wizard. The form validation and submission for each library are the same as what we covered above. I attached these sandbox demos for integration with each library.</p>

<iframe src="https://codesandbox.io/embed/multi-step-form-wizard-template-5tbqt?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Multi Step Form Wizard Template"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<iframe src="https://codesandbox.io/embed/formik-multi-step-form-bydi4?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Formik Multi Step Form"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<iframe src="https://codesandbox.io/embed/react-hook-form-multi-step-form-650xj?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="React Hook Form Multi Step Form"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<iframe src="https://codesandbox.io/embed/react-final-form-multi-step-form-4mu4n?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="React Final Form Multi Step Form"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<iframe src="https://codesandbox.io/embed/unform-multi-step-form-2lujj?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Unform Multi Step Form"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

<h2 id="summary">Summary</h2>

<p>In comparing these 4 form libraries, we have considered different factors. The image below is a tabular representation of how these libraries stand against each other.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="739"
			height="443"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png"
			
			sizes="100vw"
			alt="Comparison summary table"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Comparison summary table. (<a href='https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I use either Formik or RHF in handling forms in my projects. These are my top choices because they are the most popular, have the clearest and most extensive documentation, and the most learning resources in terms of YouTube videos and articles.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Forms will remain a critical part of how users interact with the web, and these libraries, among others, have done a good job in creating form management solutions for the common developer use cases, and much more.</p>

<h3 id="resources">Resources</h3>

<ul>
<li><a href="https://surveyjs.io/documentation">SurveyJS Documentation</a></li>
<li><a href="https://formik.org/docs/overview">Formik Docs</a></li>
<li><a href="https://react-hook-form.com/api">React Hook Form Docs</a></li>
<li><a href="https://final-form.org/docs/react-final-form/getting-started">React Final Form Docs</a></li>
<li><a href="https://unform.dev/quick-start">Unform Docs</a></li>
<li><a href="https://github.com/jquense/yup#api">Yup Docs</a></li>
<li><a href="https://www.smashingmagazine.com/2020/10/react-validation-formik-yup/">React Form Validation With Formik And Yup</a> by Nefe Emadamerho-Atori for <em>Smashing Magazine</em></li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(ks, vf, yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Atila Fassina</author><title>Understanding App Directory Architecture In Next.js</title><link>https://www.smashingmagazine.com/2023/02/understanding-app-directory-architecture-next-js/</link><pubDate>Wed, 01 Feb 2023 11:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/02/understanding-app-directory-architecture-next-js/</guid><description>The new App Directory architecture has been the main character of the recent Next.js release, which keeps bringing up many questions. In this article, Atila Fassina explores the advantages and pitfalls of this new strategy and reflects on whether you should use it in production now or not.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/02/understanding-app-directory-architecture-next-js/" />
              <title>Understanding App Directory Architecture In Next.js</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Understanding App Directory Architecture In Next.js</h1>
                  
                    
                    <address>Atila Fassina</address>
                  
                  <time datetime="2023-02-01T11:00:00&#43;00:00" class="op-published">2023-02-01T11:00:00+00:00</time>
                  <time datetime="2023-02-01T11:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Since <a href="https://nextjs.org/blog/next-13">Next.js 13 release</a>, there’s been some debate about how stable the shiny new features packed in the announcement are. On “<a href="https://www.smashingmagazine.com/2022/11/whats-new-nextjs-13/">What’s New in Next.js 13</a>?” we have covered the release announced and established that though carrying some interesting experiments, Next.js 13 is definitely stable. And since then, most of us have seen a very clear landscape when it comes to the new <code>&lt;Link&gt;</code> and <code>&lt;Image&gt;</code> components, and even the (still beta) <code>@next/font</code>; these are all good to go, instant profit. <strong>Turbopack</strong>, as clearly stated in the announcement, is still alpha: aimed strictly for <strong>development</strong> builds and still heavily under development. Whether you can or can’t use it in your daily routine depends on your stack, as there are integrations and optimizations still somewhere on the way. This article’s scope is strictly about the main character of the announcement: the new App Directory architecture (AppDir, for short).</p>

<p>Because the <strong>App directory</strong> is the one that keeps bringing questions due to it being partnered with an important evolution in the React ecosystem &mdash; React Server Components &mdash; and with edge runtimes. It clearly is the shape of the future of our Next.js apps. It is <strong>experimental</strong> though, and its <a href="https://beta.nextjs.org/docs/app-directory-roadmap">roadmap</a> is not something we can consider will be done in the next few weeks. So, should you use it in production now? What advantages can you get out of it, and what are the pitfalls you may find yourself climbing out of? As always, the answer in software development is the same: it depends.</p>

<h2 id="what-is-the-app-directory-anyway">What Is The App Directory Anyway?</h2>

<p>It is the new strategy for handling routes and rendering views in Next.js. It is made possible by a couple of different features tied together, and it is built to make the most out of React concurrent features (yes, we are talking about React Suspense). It brings, though, a big paradigm shift in how you think about components and pages in a Next.js app. This new way of building your app has <strong>a lot</strong> of very welcomed improvements to your architecture. Here’s a short, non-exhaustive list:</p>

<ul>
<li>Partial Routing.

<ul>
<li>Route Groups.</li>
<li>Parallel Routes.</li>
<li>Intercepting Routes.</li>
</ul></li>
<li>Server Components vs. Client Components.</li>
<li>Suspense Boundaries.</li>
<li>And much more, check the <a href="https://beta.nextjs.org/docs#features-overview">features overview</a> in the new documentation.</li>
</ul>

<h3 id="a-quick-comparison">A Quick Comparison</h3>

<p>When it comes to the current routing and rendering architecture (in the Pages directory), developers were required to think of data fetching per route.</p>

<ul>
<li><code>getServerSideProps</code>: Server-Side Rendered;</li>
<li><code>getStaticProps</code>: Server-Side Pre-Rendered and/or Incremental Static Regeneration;</li>
<li><code>getStaticPaths</code> + <code>getStaticProps</code>: Server-Side Pre-Rendered or Static Site Generated.</li>
</ul>

<p>Historically, it hadn’t yet been possible to choose the rendering strategy on a per-page basis. Most apps were either going full Server-Side Rendering or full Static Site Generation. Next.js created enough abstractions that made it a standard to think of routes individually within its architecture.</p>

<p>Once the app reaches the browser, hydration kicks in, and it’s possible to have routes collectively sharing data by wrapping our <code>_app</code> component in a React Context Provider. This gave us tools to <em>hoist</em> data to the top of our rendering tree and cascade it down toward the leaves of our app.</p>

<pre><code class="language-javascript">import { type AppProps } from 'next/app';

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
        &lt;SomeProvider&gt;
            &lt;Component {...pageProps} /&gt;
        &lt;/SomeProvider&gt;
}
</code></pre>

<p>The ability to render and organize required data per route made this approach an almost good tool for when data absolutely needed to be available globally in the app. And while this strategy will allow data to spread throughout the app, wrapping everything in a Context Provider bundles hydration to the root of your app. It is not possible anymore to render any branches on that tree (any route within that Provider context) on the server.</p>

<p>Here, enters the <strong>Layout Pattern</strong>. By creating wrappers around pages, we could opt in or out of rendering strategies per route again instead of doing it once with an app-wide decision. Read more on how to manage states in the Pages Directory on the article “<a href="https://www.smashingmagazine.com/2021/08/state-management-nextjs/">State Management in Next.js</a>” and on the <a href="https://nextjs.org/docs/basic-features/layouts">Next.js documentation</a>.</p>

<p>The <strong>Layout Pattern</strong> proved to be a great solution. Being able to granularly define rendering strategies is a very welcomed feature. So the App directory comes in to put the layout pattern front and center. As a first-class citizen of Next.js architecture, it enables enormous improvements in terms of performance, security, and data handling.</p>

<p>With React concurrent features, it’s now possible to stream components to the browser and let each one handle its own data. So rendering strategy is even more granular now &mdash; instead of page-wide, it’s component-based. Layouts are nested by default, which makes it more clear to the developer what impacts each page based on the file-system architecture. And on top of all that, it is mandatory to explicitly turn a component client-side (via the “use client” directive) in order to use a Context.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p><strong>Web forms</strong> are at the center of every meaningful interaction. Meet Adam Silver&rsquo;s <strong><a href="https://www.smashingmagazine.com/printed-books/form-design-patterns/">Form Design Patterns</a></strong>, a practical guide to <strong>designing and building forms</strong> for the web.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/form-design-patterns/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/form-design-patterns/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/64e57b41-b7f1-4ae3-886a-806cce580ef9/form-design-patterns-shop-image-1-1.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/51e0f837-d85d-4b28-bfab-1c9a47f0ce33/form-design-patterns-shop-image.png"
    alt="Feature Panel"
    width="481"
    height="698"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="building-blocks-of-the-app-directory">Building Blocks Of The App Directory</h2>

<p>This architecture is built around the <strong>Layout Per Page Architecture</strong>. Now, there is no <code>_app</code>, neither is there a <code>_document</code> component. They have both been replaced by the root <code>layout.jsx</code> component. As you would expect, that’s a special layout that will wrap up your entire application.</p>

<pre><code class="language-javascript">export function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        &lt;html lang="en"&gt;
            &lt;body&gt;
                {children}
            &lt;/body&gt;
        &lt;/html&gt;
}
</code></pre>

<p>The <strong>root layout</strong> is our way to manipulate the HTML returned by the server to the entire app at once. It is a server component, and it <strong>does not</strong> render again upon navigation. This means any data or state in a layout will persist throughout the lifecycle of the app.</p>

<p>While the <strong>root layout</strong> is a special component for our entire app, we can also have root components for other building blocks:</p>

<ul>
<li><code>loading.jsx</code>: to define the Suspense Boundary of an entire route;</li>
<li><code>error.jsx</code>: to define the Error Boundary of our entire route;</li>
<li><code>template.jsx</code>: similar to the layout, but re-renders on every navigation. Especially useful to handle state between routes, such as in or out transitions.</li>
</ul>

<p>All of those components and conventions are <strong>nested by default</strong>. This means that <code>/about</code> will be nested within the wrappers of <code>/</code>  automatically.</p>

<p>Finally, we are also required to have a <code>page.jsx</code> for every route as it will define the main component to render for that URL segment (as known as the place you put your components!). These are obviously not nested by default and will only show in our DOM when there’s an exact match to the URL segment they correspond to.</p>

<p>There is much more to the architecture (and even more coming!), but this should be enough to get your mental model right before considering migrating from the <strong>Pages directory</strong> to the <strong>App directory</strong> in production. Make sure to check on the official <a href="https://beta.nextjs.org/docs/upgrade-guide">upgrade guide</a> as well.</p>

<h3 id="server-components-in-a-nutshell">Server Components In A Nutshell</h3>

<p>React Server Components allow the app to leverage infrastructure towards better performance and overall user experience. For example, the immediate improvement is on bundle size since RSC won’t carry over their dependencies to the final bundle. Because they’re rendered in the server, any kind of parsing, formatting, or component library will remain on the server code. Secondly, thanks to their asynchronous nature, Server Components are <strong>streamed</strong> to the client. This allows the rendered HTML to be progressively enhanced on the browser.</p>

<p>So, Server Components lead to a more predictable, cacheable, and constant size of your final bundle breaking the linear correlation between app size and bundle size. This immediately puts RSC as a best practice versus traditional React components (which are now referred to as client components to ease disambiguation).</p>

<p>On Server Components, fetching data is also quite flexible and, in my opinion, feels closer to vanilla JavaScript &mdash; which always smooths the learning curve. For example, understanding the JavaScript runtime makes it possible to define data-fetching as either parallel or sequential and thus have more fine-grained control on the resource loading waterfall.</p>

<ul>
<li><strong>Parallel Data Fetching</strong>, waiting for all:</li>
</ul>

<pre><code class="language-javascript">import TodoList from './todo-list'

async function getUser(userId) {
  const res = await fetch(`https://&lt;some-api&gt;/user/${userId}`);
  return res.json()
}

async function getTodos(userId) {
  const res = await fetch(`https://&lt;some-api&gt;/todos/${userId}/list`);
  return res.json()
}

export default async function Page({ params: { userId } }) {
  // Initiate both requests in parallel.
  const userResponse = getUser(userId)
  const  = getTodos(username)

  // Wait for the promises to resolve.
  const [user, todos] = await Promise.all([userResponse, todosResponse])

  return (
    &lt;&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
      &lt;TodoList list={todos}&gt;&lt;/TodoList&gt;
    &lt;/&gt;
  )
}
</code></pre>

<ul>
<li><strong>Parallel</strong>, waiting for one request, streaming the other:</li>
</ul>

<pre><code class="language-javascript">async function getUser(userId) {
  const res = await fetch(`https://&lt;some-api&gt;/user/${userId}`);
  return res.json()
}

async function getTodos(userId) {
  const res = await fetch(`https://&lt;some-api&gt;/todos/${userId}/list`);
  return res.json()
}

export default async function Page({ params: { userId } }) {
  // Initiate both requests in parallel.
  const userResponse = getUser(userId)
  const todosResponse = getTodos(userId)

  // Wait only for the user.
  const user = await userResponse

  return (
    &lt;&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
            &lt;Suspense fallback={&lt;div&gt;Fetching todos...&lt;/div&gt;}&gt;
          &lt;TodoList listPromise={todosResponse}&gt;&lt;/TodoList&gt;
            &lt;/Suspense&gt;
    &lt;/&gt;
  )
}

async function TodoList ({ listPromise }) {
  // Wait for the album's promise to resolve.
  const todos = await listPromise;

  return (
    &lt;ul&gt;
      {todos.map(({ id, name }) =&gt; (
        &lt;li key={id}&gt;{name}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}
</code></pre>

<p>In this case, <code>&lt;TodoList&gt;</code> receives an in-flight Promise and needs to <code>await</code> it before rendering. The app will render the <strong>suspense fallback</strong> component until it’s all done.</p>

<ul>
<li><strong>Sequential Data Fetching</strong> fires one request at a time and awaits for each:</li>
</ul>

<pre><code class="language-javascript">async function getUser(username) {
  const res = await fetch(`https://&lt;some-api&gt;/user/${userId}`);
  return res.json()
}

async function getTodos(username) {
  const res = await fetch(`https://&lt;some-api&gt;/todos/${userId}/list`);
  return res.json()
}

export default async function Page({ params: { userId } }) {
  const user = await getUser(userId)
  

  return (
    &lt;&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
            &lt;Suspense fallback={&lt;div&gt;Fetching todos...&lt;/div&gt;}&gt;
            &lt;TodoList userId={userId} /&gt;
            &lt;/Suspense&gt;
    &lt;/&gt;
  )
}

async function TodoList ({ userId }) {
  const todos = await getTodos(userId);

  return (
    &lt;ul&gt;
      {todos.map(({ id, name }) =&gt; (
        &lt;li key={id}&gt;{name}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}
</code></pre>

<p>Now, <code>Page</code> will fetch and wait on <code>getUser</code>, then it will start rendering. Once it reaches <code>&lt;TodoList&gt;</code>, it will fetch and wait on <code>getTodos</code>. This is still more granular than what we are used to it with the Pages directory.</p>

<p>Important things to note:</p>

<ul>
<li>Requests fired within the same component scope will be fired in parallel (more about this at <a href="#extended-fetch-api">Extended Fetch API</a> below).</li>
<li>Same requests fired within the same server runtime will be deduplicated (only one is actually happening, the one with the shortest cache expiration).</li>
<li>For requests that won’t use <code>fetch</code> (such as third-party libraries like SDKs, ORMs, or database clients), route caching will not be affected unless manually configured via <a href="https://beta.nextjs.org/docs/data-fetching/fetching#segment-cache-configuration">segment cache configuration</a>.</li>
</ul>

<pre><code class="language-javascript">export const revalidate = 600; // revalidate every 10 minutes

export default function Contributors({
  params
}: {
  params: { projectId: string };
}) {
    const { projectId }  = params
    const { contributors } = await myORM.db.workspace.project({ id: projectId })

  return &lt;ul&gt;{&#42;/ ... &#42;/}&lt;/ul&gt;;
}
</code></pre>

<p>To point out how much more control this gives developers: when within the pages directory, rendering would be blocked until all data is available. When using <code>getServerSideProps</code>, the user would still see the loading spinner until data for the entire route is available. To mimic this behavior in the App directory, the fetch requests would need to happen in the <code>layout.tsx</code> for that route, so always avoid doing it. An “all or nothing” approach is rarely what you need, and it leads to worse perceived performance as opposed to this granular strategy.</p>

<div class="partners__lead-place"></div>

<h2 id="extended-fetch-api">Extended Fetch API</h2>

<p>The syntax remains the same: <code>fetch(route, options)</code>. But according to the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Web Fetch Spec</a>, the <code>options.cache</code> will determine how this API will interact with the <strong>browser cache.</strong> But in Next.js, it will interact with the <a href="https://beta.nextjs.org/docs/data-fetching/fundamentals#caching-data">framework server-side HTTP Cache</a>.</p>

<p>When it comes to the extended <strong>Fetch API</strong> for Next.js and its cache policy, two values are important to understand:</p>

<ul>
<li><code>force-cache</code>: the default, looks for a fresh match and returns it.</li>
<li><code>no-store</code> or <code>no-cache</code>: fetches from the remote server on every request.</li>
<li><code>next.revalidate</code>: the same syntax as ISR, sets a hard threshold to consider the resource fresh.</li>
</ul>

<pre><code class="language-javascript">fetch(`https://route`, { cache: 'force-cache', next: { revalidate: 60 } })
</code></pre>

<p>The caching strategy allows us to categorize our requests:</p>

<ul>
<li><strong>Static Data</strong>: persist longer. E.g., blog post.</li>
<li><strong>Dynamic Data:</strong> changes often and/or is a result of user interaction. E.g., comments section, shopping cart.</li>
</ul>

<p>By default, every data is considered <strong>static data</strong>. This is due to the fact <code>force-cache</code> is the default caching strategy. To opt out of it for fully dynamic data, it’s possible to define <code>no-store</code> or <code>no-cache</code>.</p>

<p>If a dynamic function is used (e.g., setting cookies or headers), the default will switch from <code>force-cache</code> to <code>no-store</code>!</p>

<p>Finally, to implement something more similar to Incremental Static Regeneration, you’ll need to use <code>next.revalidate</code>. With the benefit that instead of being defined for the entire route, it only defines the component it’s a part of.</p>

<h2 id="migrating-from-pages-to-app">Migrating From Pages To App</h2>

<p>Porting logic from <strong>Pages directory</strong> to <strong>Apps directory</strong> may look like a lot of work, but Next.js has worked prepared to allow both architectures to coexist, and thus <strong>migration can be done incrementally</strong>. Additionally, there is a very good <strong><a href="https://beta.nextjs.org/docs/upgrade-guide#migrating-from-pages-to-app">migration guide</a></strong> in the documentation; I recommend you to read it fully before jumping into a refactoring.</p>

<p>Guiding you through the migration path is beyond the scope of this article and would make it redundant to the docs. Alternatively, in order to add value on top of what the official documentation offers, I will try to provide insight into the friction points my experience suggests you will find.</p>

<h3 id="the-case-of-react-context">The Case Of React Context</h3>

<p>In order to provide all the benefits mentioned above in this article, RSC can’t be interactive, which means they don’t have hooks. Because of that, we have decided to push our client-side logic to the leaves of our rendering tree as late as possible; once you add interactiveness, children of that component will be client-side.</p>

<p>In a few cases pushing some components will not be possible (especially if some key functionality depends on React Context, for example). Because most libraries are prepared to defend their users against Prop Drilling, many create context providers to skip components from root to distant descendants. So ditching React Context entirely may cause some external libraries not to work well.</p>

<p>As a temporary solution, there is an escape hatch to it. A client-side wrapper for our providers:</p>

<pre><code class="language-javascript">// /providers.jsx
‘use client’

import { type ReactNode, createContext } from 'react';

const SomeContext = createContext();

export default function ThemeProvider({ children }: { children: ReactNode }) {
  return (
    &lt;SomeContext.Provider value="data"&gt;
      {children}
    &lt;/SomeContext.Provider&gt;
  );
}
</code></pre>

<p>And so the layout component will not complain about skipping a client component from rendering.</p>

<pre><code class="language-javascript">// app/.../layout.jsx
import { type ReactNode } from 'react';
import Providers from ‘./providers’;

export default function Layout({ children }: { children: ReactNode }) {
    return (
    &lt;Providers&gt;{children}&lt;/Providers&gt;
  );
}
</code></pre>

<p>It is important to realize that once you do this, the entire branch will become client-side rendered. This approach will take everything within the <code>&lt;Providers&gt;</code> component to not be rendered on the server, so use it only as a last resort.</p>

<h3 id="typescript-and-async-react-elements">TypeScript And Async React Elements</h3>

<p>When using <code>async/await</code> outside of Layouts and Pages, TypeScript will yield an error based on the response type it expects to match its JSX definitions. It is supported and will still work in runtime, but according to Next.js documentation, this needs to be fixed upstream in TypeScript.</p>

<p>For now, the solution is to add a comment in the above line <code>{/* @ts-expect-error Server Component */}</code>.</p>

<h3 id="client-side-fetch-on-the-works">Client-side Fetch On The Works</h3>

<p>Historically, Next.js has not had a built-in data mutation story. Requests being fired from the client side were at the developer’s own discretion to figure out. With React Server Components, this is bound for a chance; the React team is working on a <code>use</code> hook which will accept a <code>Promise</code>, then it will handle the promise and return the value directly.</p>

<p>In the future, this will supplant most bad cases of <code>useEffect</code> in the wild (more on that in the excellent talk “<a href="https://www.youtube.com/watch?v=bGzanfKVFeU">Goodbye UseEffect</a>”) and possibly be the standard for handling asynchronicity (fetching included) in client-side React.</p>

<p>For the time being, it is still recommended to rely on libraries like React-Query and SWR for your client-side fetching needs. Be especially aware of the <code>fetch</code> behavior, though!</p>

<div class="partners__lead-place"></div>

<h2 id="so-is-it-ready">So, Is It Ready?</h2>

<p>Experimenting is at the essence of moving forward, and we can’t make a nice omelet without breaking eggs. I hope this article has helped you answer this question for your own specific use case.</p>

<p>If on a greenfield project, I’d possibly take <strong>App directory</strong> for a spin and keep <strong>Page directory</strong> as a fallback or for the functionality that is critical for business. If refactoring, it would depend on how much client-side fetching I have. Few: do it; many: probably wait for the full story.</p>

<p>Let me know your thoughts on <a href="https://atila.io/twitter">Twitter</a> or in the comments below.</p>

<h3 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2022/11/optimizing-vue-app/">Optimizing A Vue App</a>”, Michelle Barker</li>
<li>“<a href="https://www.smashingmagazine.com/2022/10/nodejs-authentication-twilio-verify/">Node.js Authentication With Twilio Verify</a>”, Alexander Godwin</li>
<li>“<a href="https://www.smashingmagazine.com/2022/07/new-pattern-jamstack-segmented-rendering/">A New Pattern For The Jamstack: Segmented Rendering</a>”, Eric Burel</li>
<li>“<a href="https://www.smashingmagazine.com/2022/07/look-remix-differences-next/">A Look At Remix And The Differences With Next.js</a>”, Facundo Giuliani</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Daniel Yuschick</author><title>The Key To Good Component Design Is Selfishness</title><link>https://www.smashingmagazine.com/2023/01/key-good-component-design-selfishness/</link><pubDate>Fri, 20 Jan 2023 17:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2023/01/key-good-component-design-selfishness/</guid><description>When translating components from design to development, it’s common to find properties that relate to the content and not to the component itself. This considerate approach to component design creates complicated props, steeper learning curves, and eventual technical debt. However, the key to avoiding these pitfalls is selfish or self-interested component design.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2023/01/key-good-component-design-selfishness/" />
              <title>The Key To Good Component Design Is Selfishness</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The Key To Good Component Design Is Selfishness</h1>
                  
                    
                    <address>Daniel Yuschick</address>
                  
                  <time datetime="2023-01-20T17:00:00&#43;00:00" class="op-published">2023-01-20T17:00:00+00:00</time>
                  <time datetime="2023-01-20T17:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>When developing a new feature, what determines whether an existing component will work or not? And when a component doesn’t work, what exactly does that mean?</p>

<p>Does the component functionally not do what it’s expected to do, like a tab system that doesn’t switch to the correct panel? Or is it too rigid to support the designed content, such as a button with an icon after the content instead of before it? Or perhaps it’s too pre-defined and structured to support a slight variant, like a modal that always had a header section, now requiring a variant without one?</p>

<p>Such is the life of a component. All too often, they’re built for a narrow objective, then hastily extended for minor one-off variations again and again until it no longer <em>works</em>. At that point, a new component is created, the technical debt grows, the onboarding learning curve becomes steeper, and the maintainability of the codebase is more challenging.</p>

<p>Is this simply the inevitable lifecycle of a component? Or can this situation be averted? And, most importantly, if it <em>can</em> be averted, how?</p>

<p>Selfishness. Or perhaps, self-interest. Better yet, maybe a little bit of both.</p>

<p>Far too often, components are far too considerate. Too considerate of one another and, especially, too considerate of their own content. In order to create components that scale with a product, the name of the game is self-interest bordering on selfishness &mdash; cold-hearted, narcissistic, the-world-revolves-around-me selfishness.</p>

<p>This article isn’t going to settle the centuries-old debate about the line between self-interest and selfishness. Frankly, I’m not qualified to take part in any philosophical debate. However, what this article <em>is</em> going to do is demonstrate that building selfish components is in the best interest of every other component, designer, developer, and person consuming your content. In fact, selfish components create so much good around them that you could almost say they’re selfless.</p>

<p>I don’t know 🤷‍♀️ Let’s look at some components and decide for ourselves.</p>

<p><strong>Note</strong>: <em>All code examples and demos in this article will be based on React and TypeScript. However, the concepts and patterns are framework agnostic.</em></p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="/printed-books/image-optimization/">Image Optimization</a></strong>, Addy Osmani’s new practical guide to optimizing and delivering <strong>high-quality images</strong> on the web. Everything in one single <strong>528-pages</strong> book.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2c669cf1-c6ef-4c87-9901-018b04f7871f/image-optimization-shop-cover-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/87fd0cfa-692e-459c-b2f3-15209a1f6aa7/image-optimization-shop-cover-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="the-consideration-iterations">The Consideration Iterations</h2>

<p>Perhaps, the best way to demonstrate a considerate component is by walking through the lifecycle of one. We’ll be able to see how they start small and functional but become unwieldy once the design evolves. Each iteration backs the component further into a corner until the design and needs of the product outgrow the capabilities of the component itself.</p>

<p>Let’s consider the modest <code>Button</code> component. It’s deceptively complex and quite often trapped in the consideration pattern, and therefore, a great example of working through.</p>

<h3 id="iteration-1">Iteration 1</h3>

<p>While these sample designs are quite barebones, like not showing various <code>:hover</code>, <code>:focus</code>, and <code>disabled</code> states, they do showcase a simple button with two color themes.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="280"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg"
			
			sizes="100vw"
			alt="A sample button design with two color variations"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Barebones button design. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/1-barebones-button-design.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>At first glance, it’s possible the resulting <code>Button</code> component could be as barebones as the design.</p>

<div class="break-out">

<pre><code class="language-javascript">// First, extend native HTML button attributes like onClick and disabled from React.
type ButtonProps = React.ComponentPropsWithoutRef&lt;"button"&gt; & {
  text: string;
  theme: 'primary' | 'secondary';
}
</code></pre>
</div>

<pre><code class="language-html">&lt;Button
  onClick={someFunction}
  text="Add to cart"
  theme="primary"
/&gt;
</code></pre>

<p>It’s possible, and perhaps even likely, that we’ve all seen a <code>Button</code> component like this. Maybe we’ve even made one like it ourselves. Some of the namings may be different, but the props, or the API of the <code>Button</code>, are roughly the same.</p>

<p>In order to meet the requirements of the design, the <code>Button</code> defines props for the <code>theme</code> and <code>text</code>. This first iteration works and meets the current needs of both the design and the product.</p>

<p>However, the current needs of the design and product are rarely the final needs. When the next design iterations are created, the <em>Add to cart</em> button now requires an icon.</p>

<h3 id="iteration-2">Iteration 2</h3>

<p>After validating the UI of the product, it was decided that adding an icon to the <em>Add to cart</em> button would be beneficial. The designs explain, though, that not every button will include an icon.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="280"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg"
			
			sizes="100vw"
			alt="A sample button design with multiple colors and new icon variant"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Button design iteration with a new icon variant. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/2-button-design-iteration-new-icon-variant.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Returning to our <code>Button</code> component, its props can be extended with an optional <code>icon</code> prop which maps to the name of an icon to conditionally render.</p>

<pre><code class="language-javascript">type ButtonProps = {
  theme: 'primary' | 'secondary';
  text: string;
  icon?: 'cart' | '...all-other-potential-icon-names';
}
</code></pre>

<pre><code class="language-html">&lt;Button
  theme="primary"
  onClick={someFunction}
  text="Add to cart"
  icon="cart"
/&gt;
</code></pre>

<p>Whew! Crisis averted.</p>

<p>With the new <code>icon</code> prop, the <code>Button</code> can now support variants with or without an icon. Of course, this assumes the icon will always be shown at the end of the text, which, to the surprise of nobody, is not the case when the next iteration is designed.</p>

<h3 id="iteration-3">Iteration 3</h3>

<p>The previous <code>Button</code> component implementation included the icon at the text’s end, but the new designs require an icon to optionally be placed at the start of the text. The single <code>icon</code> prop will no longer fit the needs of the latest design requirements.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="280"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg"
			
			sizes="100vw"
			alt="A sample button design which includes a third color variation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Updated button design variants with multiple icon placements. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/3-updated-button-design-variants-multiple%20icon-placements.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>There are a few different directions that can be taken to meet this new product requirement. Maybe an <code>iconPosition</code> prop can be added to the <code>Button</code>. But what if there comes a need to have an icon on both sides? Maybe our <code>Button</code> component can get ahead of this assumed requirement and make a few changes to the props.</p>

<p>The single <code>icon</code> prop will no longer fit the needs of the product, so it’s removed. In its place, two new props are introduced, <code>iconAtStart</code> and <code>iconAtEnd</code>.</p>

<pre><code class="language-javascript">type ButtonProps = {
  theme: 'primary' | 'secondary' | 'tertiary';
  text: string;
  iconAtStart?: 'cart' | '...all-other-potential-icon-names';
  iconAtEnd?: 'cart' | '...all-other-potential-icon-names';
}
</code></pre>

<p>After refactoring the existing uses of <code>Button</code> in the codebase to use the new props, another crisis is averted. Now, the <code>Button</code> has some flexibility. It’s all hardcoded and wrapped in conditionals within the component itself, but surely, what the UI doesn’t know can’t hurt it.</p>

<p>Up until this point, the <code>Button</code> icons have always been the same color as the text. It seems reasonable and like a reliable default, but let’s throw a wrench into this well-oiled component by defining a variation with a contrasting color icon.</p>

<h3 id="iteration-4">Iteration 4</h3>

<p>In order to provide a sense of feedback, this confirmation UI stage was designed to be shown temporarily when an item has been added to the cart successfully.</p>

<p>Maybe this is a time when the development team chooses to push back against the product requirements. But despite the push, the decision is made to move forward with providing color flexibility to <code>Button</code> icons.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="280"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg"
			
			sizes="100vw"
			alt="A sample button design now with an icon of a different color"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A button design iteration with a contrasting icon color. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/4-button-design-iteration-contrasting%20icon-color.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Again, multiple approaches can be taken for this. Maybe an <code>iconClassName</code> prop is passed into the <code>Button</code> to have greater control over the icon’s appearance. But there are other product development priorities, and instead, a quick fix is done.</p>

<p>As a result, an <code>iconColor</code> prop is added to the <code>Button</code>.</p>

<pre><code class="language-javascript">type ButtonProps = {
  theme: 'primary' | 'secondary' | 'tertiary';
  text: string;
  iconAtStart?: 'cart' | '...all-other-potential-icon-names';
  iconAtEnd?: 'cart' | '...all-other-potential-icon-names';
  iconColor?: 'green' | '...other-theme-color-names';
}
</code></pre>

<p>With the quick fix in place, the <code>Button</code> icons can now be styled differently than the text. The UI can provide the designed confirmation, and the product can, once again, move forward.</p>

<p>Of course, as product requirements continue to grow and expand, so do their designs.</p>

<h3 id="iteration-5">Iteration 5</h3>

<p>With the latest designs, the <code>Button</code> must now be used with only an icon. This can be done in a few different approaches, yet again, but all of them require some amount of refactoring.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="345"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg"
			
			sizes="100vw"
			alt="A sample button design with only an icon as its content"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Design iteration for an icon-only button. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/5-design-iteration-icon-only-button.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Perhaps a new <code>IconButton</code> component is created, duplicating all other button logic and styles into two places. Or maybe that logic and styles are centralized and shared across both components. However, in this example, the development team decides to keep all the variants in the same <code>Button</code> component.</p>

<p>Instead, the <code>text</code> prop is marked as optional. This could be as quick as marking it as optional in the props but could require additional refactoring if there’s any logic expecting the <code>text</code> to exist.</p>

<p>But then comes the question, if the <code>Button</code> is to have only an icon, which icon prop should be used? Neither <code>iconAtStart</code> nor <code>iconAtEnd</code> appropriately describes the <code>Button</code>. Ultimately, it’s decided to bring the original <code>icon</code> prop back and use <em>it</em> for the icon-only variant.</p>

<pre><code class="language-javascript">type ButtonProps = {
  theme: 'primary' | 'secondary' | 'tertiary';
  iconAtStart?: 'cart' | '...all-other-potential-icon-names';
  iconAtEnd?: 'cart' | '...all-other-potential-icon-names';
  iconColor?: 'green' | '...other-theme-color-names';
  icon?: 'cart' | '...all-other-potential-icon-names';
  text?: string;
}
</code></pre>

<p>Now, the <code>Button</code> API is getting confusing. Maybe a few comments are left in the component to explain when and when not to use specific props, but the learning curve is growing steeper, and the potential for error is increasing.</p>

<p>For example, without adding great complexity to the <code>ButtonProps</code> type, there is no stopping a person from using the <code>icon</code> and <code>text</code> props at the same time. This could either break the UI or be resolved with greater conditional complexity within the <code>Button</code> component itself. Additionally, the <code>icon</code> prop can be used with either or both of the <code>iconAtStart</code> and <code>IconAtEnd</code> props as well. Again, this could either break the UI or be resolved with even more layers of conditionals within the component.</p>

<p>Our beloved <code>Button</code> has become quite unmanageable at this point. Hopefully, the product has reached a point of stability where no new changes or requirements will ever happen again. Ever.</p>

<h3 id="iteration-6">Iteration 6</h3>

<p>So much for never having any more changes. 🤦</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="345"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg"
			
			sizes="100vw"
			alt="A sample button design with text content in different font formats"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Final Button iteration with formatted content. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/6-final-button-iteration-formatted-content.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>This next and final iteration of the <code>Button</code> is the proverbial straw that breaks the camel’s back. In the <em>Add to cart</em> button, if the current item is already in the cart, we want to show the quantity of which on the button. On the surface, this is a straightforward change of dynamically building the <code>text</code> prop string. But the component breaks down because the current item count requires a different font weight and an underline. Because the <code>Button</code> accepts only a plain text string and no other child elements, the component no longer <em>works</em>.</p>

<p>Would this design have broken the <code>Button</code> if this was the second iteration? Maybe not. The component and codebase were both much younger then. But the codebase has grown so much by this point that refactoring for this requirement is a mountain to climb.</p>

<p>This is when one of the following things will likely happen:</p>

<ol>
<li>Do a much larger refactor to move the <code>Button</code> away from a <code>text</code> prop to accepting <code>children</code> or accepting a component or markup as the <code>text</code> value.</li>
<li>The <code>Button</code> is split into a separate <code>AddToCart</code> button with an even more rigid API specific to this one use case. This also either duplicates any button logic and styles into multiple places or extracts them into a centralized file to share everywhere.</li>
<li>The <code>Button</code> is deprecated, and a <code>ButtonNew</code> component is created, splitting the codebase, introducing technical debt, and increasing the onboarding learning curve.</li>
</ol>

<p>Neither outcome is ideal.</p>

<p>So, where did the <code>Button</code> component go wrong?</p>

<h2 id="sharing-is-impairing">Sharing Is Impairing</h2>

<p>What is the responsibility of an HTML <code>button</code> element exactly? Narrowing down this answer will shine light onto the issues facing the previous <code>Button</code> component.</p>

<p>The responsibilities of the native HTML <code>button</code> element go no further than:</p>

<ol>
<li>Display, without opinion, whatever content is passed into it.</li>
<li>Handle native functionality and attributes such as <code>onClick</code> and <code>disabled</code>.</li>
</ol>

<p>Yes, each browser has its own version of how a <code>button</code> element may look and display content, but CSS resets are often used to strip those opinions away. As a result, the <code>button</code> element boils down to little more than a functional container for triggering events.</p>

<p>The onus of formatting any content within the <code>button</code> isn’t the responsibility of the <code>button</code> but of the content itself. The <code>button</code> shouldn’t care. The <code>button</code> should not share the responsibility for its content.</p>

<blockquote>The core issue with the considerate component design is that component props define the content and not the component itself.</blockquote>

<p>In the previous <code>Button</code> component, the first major limitation was the <code>text</code> prop. From the first iteration, a limitation was placed on the content of the <code>Button</code>. While the <code>text</code> prop fit with the designs at that stage, it immediately deviated from the two core responsibilities of the native HTML <code>button</code>. It immediately forced the <code>Button</code> to be aware of and responsible for its content.</p>

<p>In the following iterations, the icon was introduced. While it seemed reasonable to bake a conditional icon into the <code>Button</code>, also doing so deviated from the core <code>button</code> responsibilities. Doing so limited the use cases of the component. In later iterations, the icon needed to be available in different positions, and the <code>Button</code> props were forced to expand to style the icon.</p>

<p>When the component is responsible for the content it displays, it needs an API that can accommodate all content variations. Eventually, that API will break down because the content will forever and always change.</p>

<h2 id="introducing-the-me-in-team">Introducing The Me In Team</h2>

<p>There’s an adage used in all team sports, “There’s no ‘I’ in a team.” While this mindset is noble, some of the greatest individual athletes have embodied other ideas.</p>

<p>Michael Jordan famously responded with his own perspective, <em>“There’s an ‘I’ in win.”</em> The late Kobe Bryant had a similar idea, <em>“There’s an ‘M-E’ in [team].”</em></p>

<p>Our original <code>Button</code> component was a team player. It shared the responsibility of its content until it reached the point of deprecation. How could the <code>Button</code> have avoided such constraints by embodying a <em>“M-E in team”</em> attitude?</p>

<div class="partners__lead-place"></div>

<h2 id="me-myself-and-ui">Me, Myself, And UI</h2>

<blockquote>When the component is responsible for the content it displays, it will break down because the content will forever and always change.</blockquote>

<p>How would a selfish component design approach have changed our original <code>Button</code>?</p>

<p>Keeping the two core responsibilities of the HTML <code>button</code> element in mind, the structure of our <code>Button</code> component would have immediately been different.</p>

<div class="break-out">

<pre><code class="language-javascript">// First, extend native HTML button attributes like onClick and disabled from React.
type ButtonProps = React.ComponentPropsWithoutRef&lt;"button"&gt; & {
  theme: 'primary' | 'secondary' | 'tertiary';
}
</code></pre>
</div>

<pre><code class="language-html">&lt;Button
  onClick={someFunction}
  theme="primary"
&gt;
  &lt;span&gt;Add to cart&lt;/span&gt;
&lt;/Button&gt;
</code></pre>

<p>By removing the original <code>text</code> prop in lieu of limitless <code>children</code>, the <code>Button</code> is able to align with its core responsibilities. The <code>Button</code> can now act as little more than a container for triggering events.</p>

<p>By moving the <code>Button</code> to its native approach of supporting child content, the various icon-related props are no longer required. An icon can now be rendered anywhere within the <code>Button</code> regardless of size and color. Perhaps the various icon-related props could be extracted into their own selfish <code>Icon</code> component.</p>

<pre><code class="language-html">&lt;Button
  onClick={someFunction}
  theme="primary"
&gt;
  &lt;Icon name="cart" /&gt;
  &lt;span&gt;Add to cart&lt;/span&gt;
&lt;/Button&gt;
</code></pre>

<p>With the content-specific props removed from the <code>Button</code>, it can now do what all selfish characters do best, think about itself.</p>

<div class="break-out">

<pre><code class="language-javascript">// First, extend native HTML button attributes like onClick and disabled from React.
type ButtonProps = React.ComponentPropsWithoutRef&lt;"button"&gt; & {
  size: 'sm' | 'md' | 'lg';
  theme: 'primary' | 'secondary' | 'tertiary';
  variant: 'ghost' | 'solid' | 'outline' | 'link'
}
</code></pre>
</div>

<p>With an API specific to itself and independent content, the <code>Button</code> is now a maintainable component. The self-interest props keep the learning curve minimal and intuitive while retaining great flexibility for various <code>Button</code> use cases.</p>

<p><code>Button</code> icons can now be placed at either end of the content.</p>

<pre><code class="language-html">&lt;Button
  onClick={someFunction}
  size="md"
  theme="primary"
  variant="solid"
&gt;
  &lt;Box display="flex" gap="2" alignItems="center"&gt;
    &lt;span&gt;Add to cart&lt;/span&gt;
    &lt;Icon name="cart" /&gt;
  &lt;/Box&gt;
&lt;/Button&gt;
</code></pre>

<p>Or, a <code>Button</code> could have only an icon.</p>

<pre><code class="language-html">&lt;Button
  onClick={someFunction}
  size="sm"
  theme="secondary"
  variant="solid"
&gt;
  &lt;Icon name="cart" /&gt;
&lt;/Button&gt;
</code></pre>

<p>However, a product may evolve over time, and selfish component design improves the ability to evolve along with it. Let’s go beyond the <code>Button</code> and into the cornerstones of selfish component design.</p>

<h2 id="the-keys-to-selfish-design">The Keys to Selfish Design</h2>

<p>Much like when creating a fictional character, it’s best to show, not tell, the reader that they’re selfish. By reading about the character’s thoughts and actions, their personality and traits can be understood. Component design can take the same approach.</p>

<p>But how exactly do we show in a component’s design and use that it is selfish?</p>

<h3 id="html-drives-the-component-design">HTML Drives The Component Design</h3>

<p>Many times, components are built as direct abstractions of native HTML elements like a <code>button</code> or <code>img</code>. When this is the case, let the native HTML element drive the design of the component.</p>

<p>Specifically, if the native HTML element accepts children, the abstracted component should as well. Every aspect of a component that deviates from its native element is something that must be learned anew.</p>

<p>When our original <code>Button</code> component deviated from the native behavior of the <code>button</code> element by not supporting child content, it not only became rigid but it required a mental model shift just to use the component.</p>

<p>There has been a lot of time and thought put into the structure and definitions of HTML elements. The wheel doesn’t need to be reinvented every time.</p>

<h3 id="children-fend-for-themselves">Children Fend For Themselves</h3>

<p>If you’ve ever read <em>Lord of the Flies</em>, you know just how dangerous it can be when a group of children is forced to fend for themselves. However, in the case of selfish component design, we’ll be doing exactly that.</p>

<p>As shown in our original <code>Button</code> component, the more it tried to style its content, the more rigid and complicated it became. When we removed that responsibility, the component was able to do a lot more but with a lot less.</p>

<p>Many elements are little more than semantic containers. It’s not often we expect a <code>section</code> element to style its content. A <code>button</code> element is just a very specific type of semantic container. The same approach can apply when abstracting it to a component.</p>

<h3 id="components-are-singularly-focused">Components Are Singularly Focused</h3>

<p>Think of selfish component design as arranging a bunch of terrible first dates. A component’s props are like the conversation that is entirely focused on them and their immediate responsibilities:</p>

<ul>
<li><strong>How do I look?</strong><br />
Props need to feed the ego of the component. In our refactored <code>Button</code> example, we did this with props like <code>size</code>, <code>theme</code>, and <code>variant</code>.</li>
<li><strong>What am I doing?</strong><br />
A component should only be interested in what it, and it alone, is doing. Again, in our refactored <code>Button</code> component, we do this with the <code>onClick</code> prop. As far as the <code>Button</code> is concerned, if there’s another click event somewhere within its content, that’s the content’s problem. The <code>Button</code> does. not. care.</li>
<li><strong>When and where am I going next?</strong><br />
Any jet-setting traveler is quick to talk about their next destination. For components like modals, drawers, and tooltips, when and where they’re going is just as gravely important. Components like these are not always rendered in the DOM. This means that in addition to knowing how they look and what they do, they need to know when and where to be. In other words, this can be described with props like <code>isShown</code> and <code>position</code>.</li>
</ul>

<h3 id="composition-is-king">Composition Is King</h3>

<p>Some components, such as modals and drawers, can often contain different layout variations. For example, some modals will show a header bar while others do not. Some drawers may have a footer with a call to action. Others may have no footer at all.</p>

<p>Instead of defining each layout in a single <code>Modal</code> or <code>Drawer</code> component with conditional props like <code>hasHeader</code> or <code>showFooter</code>, break the single component into multiple composable child components.</p>

<pre><code class="language-html">&lt;Modal&gt;
  &lt;Modal.CloseButton /&gt;
  &lt;Modal.Header&gt; ... &lt;/Modal.Header&gt;
  &lt;Modal.Main&gt; ... &lt;Modal.Main&gt;
&lt;/Modal&gt;
</code></pre>

<pre><code class="language-html">&lt;Drawer&gt;
  &lt;Drawer.Main&gt; ... &lt;/Drawer.Main&gt;
  &lt;Drawer.Footer&gt; ... &lt;/Drawer.Footer&gt;
&lt;/Drawer&gt;
</code></pre>

<p>By using component composition, each individual component can be as selfish as it wants to be and used only when and where it’s needed. This keeps the root component’s API clean and can move many props to their specific child component.</p>

<p>Let’s explore this and the other keys to selfish component design a bit more.</p>

<h2 id="you-re-so-vain-you-probably-think-this-code-is-about-you">You’re So Vain, You Probably Think This Code Is About You</h2>

<p>Perhaps the keys of selfish design make sense when looking back at the evolution of our <code>Button</code> component. Nevertheless, let’s apply them again to another commonly problematic component &mdash; the modal.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="554"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png"
			
			sizes="100vw"
			alt="A modal design for editing the display name and email address of a profile"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Edit Profile Modal. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/7-edit-profile-modal.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="555"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png"
			
			sizes="100vw"
			alt="A modal design for indicating a file has been uploaded successfully"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Upload Successful Modal. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/8-upload-successful-modal-iteration.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="553"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png"
			
			sizes="100vw"
			alt="A modal design for displaying the friends of an account"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Friends Modal. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/9-friends-modal-iteration.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For this example, we have the benefit of foresight in the three different modal layouts. This will help steer the direction of our <code>Modal</code> while applying each key of selfish design along the way.</p>

<p>First, let’s go over our mental model and break down the layouts of each design.</p>

<p>In the <strong>Edit Profile</strong> modal, there are defined header, main and footer sections. There’s also a close button. In the <strong>Upload Successful</strong> modal, there’s a modified header with no close button and a hero-like image. The buttons in the footer are also stretched. Lastly, in the <strong>Friends</strong> modal, the close button returns, but now the content area is scrollable, and there’s no footer.</p>

<p>So, what did we learn?</p>

<p>We learned that the header, main and footer sections are interchangeable. They may or may not exist in any given view. We also learned that the close button functions independently and is not tied to any specific layout or section.</p>

<p>Because our <code>Modal</code> can be comprised of interchangeable layouts and arrangements, that’s our sign to take a composable child component approach. This will allow us to plug and play pieces into the <code>Modal</code> as needed.</p>

<p>This approach allows us to very narrowly define the responsibilities of our root <code>Modal</code> component.</p>

<p><strong>Conditionally render with any combination of content layouts.</strong></p>

<p>That’s it. So long as our <code>Modal</code> is just a conditionally-rendered container, it will never need to care about or be responsible for its content.</p>

<p>With the core responsibility of our <code>Modal</code> defined, and the composable child component approach decided, let’s break down each composable piece and its role.</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>Component</th>
            <th>Role</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>`<Modal>`</td>
            <td>This is the entry point of the entire <code>Modal</code> component. This container is responsible for when and where to render, how the modal looks, and what it does, like handle accessibility considerations.</td>
        </tr>
        <tr>
            <td>`<Modal.CloseButton />`</td>
            <td>An interchangeable <code>Modal</code> child component that can be included only when needed. This component will work similarly to our refactored <code>Button</code> component. It will be responsible for how it looks, where it’s shown, and what it does.</td>
        </tr>
        <tr>
            <td>`<Modal.Header>`</td>
            <td>The header section will be an abstraction of the native HTML <code>header</code> element. It will be little more than a semantic container for any content, like headings or images, to be shown.</td>
        </tr>
    <tr>
            <td>`<Modal.Main>`</td>
            <td>The main section will be an abstraction of the native HTML <code>main</code> element. It will be little more than a semantic container for any content. </td>
        </tr>
    <tr>
            <td>`<Modal.Footer>`</td>
            <td>The footer section will be an abstraction of the native HTML <code>footer</code> element. It will be little more than a semantic container for any content. </td>
        </tr>
    </tbody>
</table>

<p>With each component and its role defined, we can start creating props to support those roles and responsibilities.</p>

<h4 id="modal"><code>Modal</code></h4>

<p>Earlier, we defined the barebones responsibility of the <code>Modal</code>, knowing when to conditionally render. This can be achieved using a prop like <code>isShown</code>. Therefore, we can use these props, and whenever it’s <code>true</code>, the <code>Modal</code> and its content will render.</p>

<pre><code class="language-javascript">type ModalProps = {
  isShown: boolean;
}
</code></pre>

<pre><code class="language-html">&lt;Modal isShown={showModal}&gt;
  ...
&lt;/Modal&gt;
</code></pre>

<p>Any styling and positioning can be done with CSS in the <code>Modal</code> component directly. There’s no need to create specific props at this time.</p>

<h4 id="modal-closebutton"><code>Modal.CloseButton</code></h4>

<p>Given our previously refactored <code>Button</code> component, we know how the <code>CloseButton</code> should work. Heck, we can even use our <code>Button</code> to build our <code>CloseButton</code> component.</p>

<div class="break-out">

<pre><code class="language-javascript">import { Button, ButtonProps } from 'components/Button';

export function CloseButton({ onClick, ...props }: ButtonProps) {
  return (
    &lt;Button {...props} onClick={onClick} variant="ghost" theme="primary" /&gt;
  )
}
</code></pre>
</div>

<pre><code class="language-html">&lt;Modal&gt;
  &lt;Modal.CloseButton onClick={closeModal} /&gt;
&lt;/Modal&gt;
</code></pre>

<h4 id="modal-header-modal-main-modal-footer"><code>Modal.Header</code>, <code>Modal.Main</code>, <code>Modal.Footer</code></h4>

<p>Each of the individual layout sections, <code>Modal.Header</code>, <code>Modal.Main</code>, and <code>Modal.Footer</code>, can take direction from their HTML equivalents, <code>header</code>, <code>main</code>, and <code>footer</code>. Each of these elements supports any variation of child content, and therefore, our components will do the same.</p>

<p>There are no special props needed. They serve only as semantic containers.</p>

<pre><code class="language-html">&lt;Modal&gt;
  &lt;Modal.CloseButton onClick={closeModal} /&gt;
  &lt;Modal.Header&gt; ... &lt;/Modal.Header&gt;
  &lt;Modal.Main&gt; ... &lt;/Modal.Main&gt;
  &lt;Modal.Footer&gt; ... &lt;/Modal.Footer&gt;
&lt;/Modal&gt;
</code></pre>

<p>With our <code>Modal</code> component and its child component defined, let’s see how they can be used interchangeably to create each of the three designs.</p>

<p><strong>Note</strong>: <em>The full markup and styles are not shown so as not to take away from the core takeaways.</em></p>

<h3 id="edit-profile-modal">Edit Profile Modal</h3>

<p>In the <strong>Edit Profile</strong> modal, we use each of the <code>Modal</code> components. However, each is used only as a container that styles and positions itself. This is why we haven’t included a <code>className</code> prop for them. Any content styling should be handled by the content itself, not our container components.</p>

<pre><code class="language-html">&lt;Modal&gt;
  &lt;Modal.CloseButton onClick={closeModal} /&gt;

  &lt;Modal.Header&gt;
    &lt;h1&gt;Edit Profile&lt;/h1&gt;
  &lt;/Modal.Header&gt;

  &lt;Modal.Main&gt;
    &lt;div className="modal-avatar-selection-wrapper"&gt; ... &lt;/div&gt;
    &lt;form className="modal-profile-form"&gt; ... &lt;/form&gt;
  &lt;/Modal.Main&gt;

  &lt;Modal.Footer&gt;
    &lt;div className="modal-button-wrapper"&gt;
      &lt;Button onClick={closeModal} theme="tertiary">Cancel&lt;/Button&gt;
      &lt;Button onClick={saveProfile} theme="secondary">Save&lt;/Button&gt;
    &lt;/div&gt;
  &lt;/Modal.Footer&gt;
&lt;/Modal&gt;
</code></pre>

<h3 id="upload-successful-modal">Upload Successful Modal</h3>

<p>Like in the previous example, the <strong>Upload Successful</strong> modal uses its components as opinionless containers. The styling for the content is handled by the content itself. Perhaps this means the buttons could be stretched by the <code>modal-button-wrapper</code> class, or we could add a <em>“how do I look?”</em> prop, like <code>isFullWidth</code>, to the <code>Button</code> component for a wider or full-width size.</p>

<pre><code class="language-html">&lt;Modal&gt;
  &lt;Modal.Header&gt;
    &lt;img src="..." alt="..." /&gt;
    &lt;h1&gt;Upload Successful&lt;/h1&gt;
  &lt;/Modal.Header&gt;

  &lt;Modal.Main&gt;
    &lt;p&gt; ... &lt;/p&gt;
    &lt;div className="modal-copy-upload-link-wrapper"&gt; ... &lt;/div&gt;
  &lt;/Modal.Main&gt;

  &lt;Modal.Footer&gt;
    &lt;div className="modal-button-wrapper"&gt;
      &lt;Button onClick={closeModal} theme="tertiary">Skip&lt;/Button&gt;
      &lt;Button onClick={saveProfile} theme="secondary">Save&lt;/Button&gt;
    &lt;/div&gt;
  &lt;/Modal.Footer&gt;
&lt;/Modal&gt;
</code></pre>

<h3 id="friends-modal">Friends Modal</h3>

<p>Lastly, our <strong>Friends</strong> modal does away with the <code>Modal.Footer</code> section. Here, it may be enticing to define the overflow styles on <code>Modal.Main</code>, but that is extending the container’s responsibilities to its content. Instead, handling those styles is better suited in the <code>modal-friends-wrapper</code> class.</p>

<pre><code class="language-html">&lt;Modal&gt;
  &lt;Modal.CloseButton onClick={closeModal} /&gt;

  &lt;Modal.Header&gt;
    &lt;h1&gt;AngusMcSix's Friends&lt;/h1&gt;
  &lt;/Modal.Header&gt;

  &lt;Modal.Main&gt;
      &lt;div className="modal-friends-wrapper"&gt;
        &lt;div className="modal-friends-friend-wrapper"&gt; ... &lt;/div&gt;
        &lt;div className="modal-friends-friend-wrapper"&gt; ... &lt;/div&gt;
        &lt;div className="modal-friends-friend-wrapper"&gt; ... &lt;/div&gt;
      &lt;/div&gt;
  &lt;/Modal.Main&gt;
&lt;/Modal&gt;
</code></pre>

<p>With a selfishly designed <code>Modal</code> component, we can accommodate evolving and changing designs with flexible and tightly scoped components.</p>

<h3 id="next-modal-evolutions">Next Modal Evolutions</h3>

<p>Given all that we’ve covered, let’s throw around some hypotheticals regarding our <code>Modal</code> and how it may evolve. How would <em>you</em> approach these design variations?</p>

<p>A design requires a fullscreen modal. How would you adjust the <code>Modal</code> to accommodate a fullscreen variation?</p>

<p>Another design is for a 2-step registration process. How could the <code>Modal</code> accommodate this type of design and functionality?</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="554"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png"
			
			sizes="100vw"
			alt="A modal design showing step one of completing an account registration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Modal registration stage 1. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/10-modal-registration-stage-1.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="554"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png"
			
			sizes="100vw"
			alt="A modal design showing step two of completing an account registration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Modal registration stage 2. (<a href='https://files.smashing.media/articles/key-good-component-design-selfishness/11-modal-registration-stage-2.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="partners__lead-place"></div>

<h2 id="recap">Recap</h2>

<p>Components are the workhorses of modern web development. Greater importance continues to be placed on component libraries, either standalone or as part of a design system. With how fast the web moves, having components that are accessible, stable, and resilient is absolutely critical.</p>

<p>Unfortunately, components are often built to do too much. They are built to inherit the responsibilities and concerns of their content and surroundings. So many patterns that apply this level of consideration break down further each iteration until a component no longer <em>works</em>. At this point, the codebase splits, more technical debt is introduced, and inconsistencies creep into the UI.</p>

<p>If we break a component down to its core responsibilities and build an API of props that only define those responsibilities, without consideration of content inside or around the component, we build components that can be resilient to change. This selfish approach to component design ensures a <strong>component is only responsible for itself and not its content</strong>. Treating components as little more than semantic containers means content can change or even move between containers without effect. The less considerate a component is about its content and its surroundings, the better for everybody &mdash; better for the content that will forever change, better for the consistency of the design and UI, which in turn is better for the people consuming that changing content, and lastly, better for the developers using the components.</p>

<p>The key to the good component design is selfishness. Being a considerate team player is the responsibility of the developer.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item></channel></rss>