<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Janarthanan Soundararajan (Jana)</title>
    <description>The latest articles on DEV Community by Janarthanan Soundararajan (Jana) (@janarthanan_soundararajan).</description>
    <link>https://dev.to/janarthanan_soundararajan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4064706%2Fa88f1e6d-2e06-462a-ae72-7d011147ba2e.png</url>
      <title>DEV Community: Janarthanan Soundararajan (Jana)</title>
      <link>https://dev.to/janarthanan_soundararajan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/janarthanan_soundararajan"/>
    <language>en</language>
    <item>
      <title>How to Avoid Unnecessary Re-renders in React Without Memoizing Everything</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Wed, 16 Sep 2026 10:40:55 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/how-to-avoid-unnecessary-re-renders-in-react-without-memoizing-everything-1adi</link>
      <guid>https://dev.to/janarthanan_soundararajan/how-to-avoid-unnecessary-re-renders-in-react-without-memoizing-everything-1adi</guid>
      <description>&lt;p&gt;Add a &lt;code&gt;console.log()&lt;/code&gt; inside a React component and you may see it run more often than expected. The first reaction is often to add &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;p&gt;That usually treats the symptom before finding the cause.&lt;/p&gt;

&lt;p&gt;A re-render is also not the same as a DOM update. During rendering, React calls components to calculate the next UI. During the commit phase, it updates only the DOM nodes that changed. A component running again can therefore be harmless. The optimization matters when repeated work is expensive or makes an interaction feel slow.&lt;/p&gt;

&lt;p&gt;Before changing the code, identify why the component rendered and how much work that render performs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a React component re-render?
&lt;/h2&gt;

&lt;p&gt;A component can render again for several reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Its own state changed.&lt;/li&gt;
&lt;li&gt;One of its ancestors rendered, causing the normal render cascade through descendants.&lt;/li&gt;
&lt;li&gt;A context it reads received a different value.&lt;/li&gt;
&lt;li&gt;An external store subscription returned an updated value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Props need a small clarification. Changed props do not independently schedule a normal child component to render. The parent renders first and supplies those props. Without memoization, the child normally renders as part of that parent render whether its props changed or not.&lt;/p&gt;

&lt;p&gt;Prop equality becomes important when the child is wrapped in &lt;code&gt;React.memo&lt;/code&gt;. React can then compare each prop with its previous value using &lt;code&gt;Object.is&lt;/code&gt; and skip the child when they are equal.&lt;/p&gt;

&lt;p&gt;React's &lt;a href="https://react.dev/learn/render-and-commit" rel="noopener noreferrer"&gt;render and commit documentation&lt;/a&gt; explains this sequence in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, decide whether the render needs fixing
&lt;/h2&gt;

&lt;p&gt;A render is unnecessary from a performance perspective only when it repeats work that has a measurable cost without producing a useful change. A small component rendering after its parent is normal React behavior. If the interaction remains responsive, adding memoization may give the code more complexity than speed.&lt;/p&gt;

&lt;p&gt;The same caution applies to inline values. An inline callback, object, or array is not automatically a performance problem. Its changing identity matters when another part of the program observes that identity, such as a memoized child, an Effect dependency, or a store subscription.&lt;/p&gt;

&lt;p&gt;The techniques below are options to apply after identifying a specific source of wasted work. They are not rules that every component should follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Move state closer to where it is used
&lt;/h2&gt;

&lt;p&gt;Start with component structure. If an input owns the only state that changes while someone types, the page does not need to own that state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Typing causes Dashboard and its descendants to render.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;VeryExpensiveTree&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move the state into a smaller component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SearchInput&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SearchInput&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;VeryExpensiveTree&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the state update starts inside &lt;code&gt;SearchInput&lt;/code&gt;. React does not need to render &lt;code&gt;Dashboard&lt;/code&gt; or &lt;code&gt;VeryExpensiveTree&lt;/code&gt; for every keystroke.&lt;/p&gt;

&lt;p&gt;This applies to other temporary UI state too. If only a modal uses a form value, it can usually own that state. A tooltip can usually own its open state. Moving either value upward is still correct when another part of the tree needs to read or control it.&lt;/p&gt;

&lt;p&gt;Yes. The idea is useful, but the explanation can be shorter. I would replace it with this:&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep expensive content outside the stateful wrapper
&lt;/h2&gt;

&lt;p&gt;If a wrapper has local state, pass large child content through &lt;code&gt;children&lt;/code&gt; instead of creating it inside the wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InteractivePanelProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;InteractivePanel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;InteractivePanelProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;highlighted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHighlighted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;highlighted&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;highlighted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHighlighted&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Toggle highlight
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InteractivePanel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;VeryExpensiveTree&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;InteractivePanel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;highlighted&lt;/code&gt; changes, &lt;code&gt;InteractivePanel&lt;/code&gt; re-renders. &lt;code&gt;Dashboard&lt;/code&gt; does not, so React can reuse the same &lt;code&gt;children&lt;/code&gt; element without rendering &lt;code&gt;VeryExpensiveTree&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;This only isolates updates caused by the wrapper's local state. &lt;code&gt;VeryExpensiveTree&lt;/code&gt; can still render when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Dashboard&lt;/code&gt; re-renders.&lt;/li&gt;
&lt;li&gt;Its own state or context changes.&lt;/li&gt;
&lt;li&gt;It is removed and mounted again through conditional rendering.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use composition to limit how far a local state update travels. It is a structural optimization, not a replacement for &lt;code&gt;React.memo&lt;/code&gt; in every case.&lt;/p&gt;

&lt;p&gt;The main improvement is the heading: “Keep expensive content outside the stateful wrapper” explains the technique more directly than “Pass stable content through composition.”&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Skip expensive cascade renders with &lt;code&gt;React.memo&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When a parent renders, its child components normally render too. &lt;code&gt;React.memo&lt;/code&gt; can skip a child when all its props remain equal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;memo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ItemListProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ItemList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ItemList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ItemListProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;React.memo&lt;/code&gt; is a reasonable option when these conditions are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The parent renders frequently.&lt;/li&gt;
&lt;li&gt;The child often receives the same props.&lt;/li&gt;
&lt;li&gt;Rendering the child performs enough work to matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not block every render. The component still renders when its own state changes or when a context it consumes changes. React also describes memoization as a performance optimization rather than a guarantee in the &lt;a href="https://react.dev/reference/react/memo" rel="noopener noreferrer"&gt;&lt;code&gt;memo&lt;/code&gt; documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Wrapping every component in &lt;code&gt;memo&lt;/code&gt; adds prop comparisons and can make the code harder to follow without improving the user experience. In a component with cheap rendering or frequently changing props, skipping memoization may be the better choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep function props stable with &lt;code&gt;useCallback&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;JavaScript creates a new function whenever the parent executes this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MemoizedItem&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function reference is different on every parent render. If &lt;code&gt;MemoizedItem&lt;/code&gt; is wrapped in &lt;code&gt;React.memo&lt;/code&gt;, that new reference prevents React from skipping it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useCallback&lt;/code&gt; can keep the reference stable until one of its dependencies changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Count: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MemoizedItem&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDelete&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The empty dependency array is correct only because this example uses a stable &lt;code&gt;deleteItem&lt;/code&gt; function declared outside the component. If the callback reads props or state, include those reactive values in its dependency list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useCallback&lt;/code&gt; does not stop &lt;code&gt;Parent&lt;/code&gt; from rendering, and caching a callback has little value when it is passed to a non-memoized child. Its common use is keeping a function prop stable for a memoized component or satisfying another Hook's dependency requirements. See the &lt;a href="https://react.dev/reference/react/useCallback" rel="noopener noreferrer"&gt;&lt;code&gt;useCallback&lt;/code&gt; reference&lt;/a&gt; for the full behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use &lt;code&gt;useMemo&lt;/code&gt; for calculations and reference stability
&lt;/h2&gt;

&lt;p&gt;Arrays and objects have the same reference problem as functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visibleItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;filter()&lt;/code&gt; returns a new array on every render. That new array breaks the prop comparison of a memoized list even if &lt;code&gt;items&lt;/code&gt; did not change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visibleItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MemoizedList&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visibleItems&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has two possible benefits. It avoids repeating a costly calculation, and it preserves the array reference so &lt;code&gt;MemoizedList&lt;/code&gt; can be skipped.&lt;/p&gt;

&lt;p&gt;Do not use &lt;code&gt;useMemo&lt;/code&gt; for every small calculation. The cache also has a cost, and React may discard cached values in some situations. Use it for performance, not correctness. The &lt;a href="https://react.dev/reference/react/useMemo" rel="noopener noreferrer"&gt;&lt;code&gt;useMemo&lt;/code&gt; documentation&lt;/a&gt; recommends measuring whether the calculation is expensive enough to justify caching.&lt;/p&gt;

&lt;p&gt;Sometimes the cleaner fix is to pass smaller props. If a child only needs &lt;code&gt;user.name&lt;/code&gt;, pass the name instead of the full &lt;code&gt;user&lt;/code&gt; object. Primitive props are often easier to keep stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Split contexts to reduce subscriptions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;React.memo&lt;/code&gt; cannot protect a component from updates to a context that it reads. When the provider receives a different value, consumers of that context render again.&lt;/p&gt;

&lt;p&gt;One large context makes unrelated consumers depend on the same update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AppContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AppContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing &lt;code&gt;notifications&lt;/code&gt; can cause a component that only reads &lt;code&gt;theme&lt;/code&gt; to render. Splitting unrelated values into separate contexts narrows the update.&lt;/p&gt;

&lt;p&gt;State and dispatch can also be separated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Dispatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RESET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StateContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppState&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DispatchContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Dispatch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AppProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DispatchContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StateContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StateContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DispatchContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ActionButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DispatchContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ActionButton must be used inside AppProvider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RESET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Reset
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;dispatch&lt;/code&gt; function returned by &lt;code&gt;useReducer&lt;/code&gt; has a stable identity. &lt;code&gt;ActionButton&lt;/code&gt; can therefore dispatch actions without subscribing to the state context.&lt;/p&gt;

&lt;p&gt;This split does not solve every context render. A component reading &lt;code&gt;StateContext&lt;/code&gt; still renders whenever the &lt;code&gt;state&lt;/code&gt; object changes. For a large shared state, split contexts by concern or use a store that supports selecting a smaller state slice. React's guide on &lt;a href="https://react.dev/learn/scaling-up-with-reducer-and-context" rel="noopener noreferrer"&gt;scaling with reducer and context&lt;/a&gt; uses the same state-and-dispatch separation.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Use &lt;code&gt;useRef&lt;/code&gt; for data that does not affect the UI
&lt;/h2&gt;

&lt;p&gt;State should represent data used during rendering. If changing a value should not change the JSX, a ref may be a better fit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Tracker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clickCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;clickCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Track click&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating &lt;code&gt;clickCount.current&lt;/code&gt; does not schedule a render. Refs are useful for timer IDs, DOM nodes, previous values, and mutable tracking data.&lt;/p&gt;

&lt;p&gt;Do not move visible data into a ref just to avoid rendering. If the count must appear on screen, it belongs in state. React will not update the UI when only &lt;code&gt;ref.current&lt;/code&gt; changes. The &lt;a href="https://react.dev/reference/react/useRef" rel="noopener noreferrer"&gt;&lt;code&gt;useRef&lt;/code&gt; documentation&lt;/a&gt; makes this distinction explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Remove Effects that only derive state
&lt;/h2&gt;

&lt;p&gt;An Effect that calculates one piece of state from another often creates an avoidable second render.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ProfileProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFullName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setFullName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component first renders with the old &lt;code&gt;fullName&lt;/code&gt;. React commits that result, runs the Effect, updates state, and renders again.&lt;/p&gt;

&lt;p&gt;Calculate the value during rendering instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ProfileProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;useMemo&lt;/code&gt; only if the derived calculation is expensive. React's &lt;a href="https://react.dev/learn/you-might-not-need-an-effect" rel="noopener noreferrer"&gt;You Might Not Need an Effect&lt;/a&gt; guide covers this pattern and other unnecessary Effect chains.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Subscribe to the smallest store value you need
&lt;/h2&gt;

&lt;p&gt;The same principle applies to Redux, Zustand, and other external stores. A component that subscribes to a large object may render for changes it never displays.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Broad subscription&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RootState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Narrow subscription&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RootState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second component depends only on &lt;code&gt;user.name&lt;/code&gt;. Whether a render is skipped depends on the store library's selector and equality behavior, so check that library's documentation before adding custom comparison functions. For example, Redux &lt;code&gt;useSelector&lt;/code&gt; and Zustand selectors do not have identical APIs or defaults.&lt;/p&gt;

&lt;p&gt;Also avoid returning a fresh object from a selector unless the library provides shallow comparison or memoized selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// New object on every selector call&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSummary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RootState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Selecting primitives separately or using a memoized selector gives the subscription a stable result when the underlying values have not changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Profile before and after optimizing
&lt;/h2&gt;

&lt;p&gt;Console logs can show that a component rendered, but they do not show whether the render was expensive. React Strict Mode may also call component functions more than once during development to detect impure rendering. That can make logs look worse than production behavior.&lt;/p&gt;

&lt;p&gt;Use React DevTools Profiler or React's &lt;a href="https://react.dev/reference/react/Profiler" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;Profiler&amp;gt;&lt;/code&gt; API&lt;/a&gt; to answer more useful questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which interaction feels slow?&lt;/li&gt;
&lt;li&gt;Which components rendered during it?&lt;/li&gt;
&lt;li&gt;How long did they take?&lt;/li&gt;
&lt;li&gt;Did memoization reduce the work?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measure a production build when comparing performance. Development checks and tooling add overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small details that can change the diagnosis
&lt;/h2&gt;

&lt;p&gt;A few cases can make render behavior look different from the simplified examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting state to its current value can let React skip the update. You may still see the component function called in some cases before React discards the result.&lt;/li&gt;
&lt;li&gt;A memoized component still renders when its own state changes or a context it reads changes.&lt;/li&gt;
&lt;li&gt;A custom comparison function for &lt;code&gt;React.memo&lt;/code&gt; can cost more than rendering the component. Deep comparisons are especially risky when the data structure can grow.&lt;/li&gt;
&lt;li&gt;Changing a component's &lt;code&gt;key&lt;/code&gt; resets its identity and state. Keys are useful for reconciliation, but changing them is not a general re-render optimization.&lt;/li&gt;
&lt;li&gt;Conditionally removing a component unmounts it. Showing it again creates a new instance, even when its JSX was previously passed through &lt;code&gt;children&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Suspense, transitions, hydration, and concurrent rendering can cause React to start, pause, retry, or discard render work. A function call in a log does not prove that React committed a DOM update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cases do not invalidate the earlier techniques. They explain why a small demo, development log, and production profile may show different numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick diagnostic matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;First option to consider&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typing in one input renders a large page&lt;/td&gt;
&lt;td&gt;Input state is owned too high in the tree&lt;/td&gt;
&lt;td&gt;Move the state into the input or form subtree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expensive child renders when its parent changes unrelated state&lt;/td&gt;
&lt;td&gt;Normal parent-to-child render cascade&lt;/td&gt;
&lt;td&gt;Use composition or &lt;code&gt;React.memo&lt;/code&gt; if the child's props remain stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memoized child still renders because of a callback prop&lt;/td&gt;
&lt;td&gt;A new function reference is passed each time&lt;/td&gt;
&lt;td&gt;Consider &lt;code&gt;useCallback&lt;/code&gt; if stable identity lets that child skip work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memoized list still renders with unchanged source data&lt;/td&gt;
&lt;td&gt;Filtering creates a new array&lt;/td&gt;
&lt;td&gt;Consider &lt;code&gt;useMemo&lt;/code&gt; when the calculation or stable reference matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A theme consumer renders after an unrelated context update&lt;/td&gt;
&lt;td&gt;One context contains unrelated changing values&lt;/td&gt;
&lt;td&gt;Split the context by concern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Updating a tracking value renders the component&lt;/td&gt;
&lt;td&gt;Non-visual data is stored in state&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;useRef&lt;/code&gt; if the value does not affect JSX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Component renders twice after props change&lt;/td&gt;
&lt;td&gt;An Effect copies or derives state&lt;/td&gt;
&lt;td&gt;Calculate the value during rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What about React Compiler?
&lt;/h2&gt;

&lt;p&gt;When React Compiler is enabled and successfully compiles the relevant components, it can apply memoization automatically and reduce the need for manual &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt;. That does not make state placement, context boundaries, or unnecessary Effects irrelevant. Those choices determine how much of the tree participates in an update and how easy the code is to understand.&lt;/p&gt;

&lt;p&gt;If the compiler is not enabled in your project, manual memoization remains useful when profiling identifies repeated expensive work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical order for optimization
&lt;/h2&gt;

&lt;p&gt;When a screen feels slow, work through the problem in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm which interaction is slow with the Profiler.&lt;/li&gt;
&lt;li&gt;Move temporary state closer to the components that use it.&lt;/li&gt;
&lt;li&gt;Remove Effects that derive state unnecessarily.&lt;/li&gt;
&lt;li&gt;Split broad contexts and store subscriptions.&lt;/li&gt;
&lt;li&gt;Consider &lt;code&gt;React.memo&lt;/code&gt; for expensive children that often receive unchanged props.&lt;/li&gt;
&lt;li&gt;Consider &lt;code&gt;useCallback&lt;/code&gt; or &lt;code&gt;useMemo&lt;/code&gt; when unstable references are defeating that memoization or the calculation itself is expensive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The shortest rule is: reduce the scope of the update before caching values and functions.&lt;/p&gt;

&lt;p&gt;React is designed to re-render components. The goal is not to reach zero re-renders. The goal is to prevent expensive work that produces no useful change for the user.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>What to Check When Reviewing AI-Generated UI</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:05:57 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/what-to-check-when-reviewing-ai-generated-ui-h28</link>
      <guid>https://dev.to/janarthanan_soundararajan/what-to-check-when-reviewing-ai-generated-ui-h28</guid>
      <description>&lt;p&gt;AI can help us build a form, a modal, or a dashboard quickly.&lt;/p&gt;

&lt;p&gt;The result may look good. The inputs accept text. The buttons respond. The layout fits the screen.&lt;/p&gt;

&lt;p&gt;But before merging the code, there is another question to ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the UI work well beyond the happy path?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some details are easy to miss during a visual review. A label may not be connected to its input. An icon button may have no accessible name. A secondary button may submit a form by accident.&lt;/p&gt;

&lt;p&gt;These are review checkpoints, not claims that every AI tool makes these mistakes. They apply to code we write ourselves too.&lt;/p&gt;

&lt;p&gt;The examples below use React, but most of the ideas come from HTML and apply across frameworks. The snippets focus on individual details rather than a complete application.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Are labels connected to their inputs?
&lt;/h3&gt;

&lt;p&gt;This looks reasonable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The text is visible, but the label and input are not connected. In JSX, use &lt;code&gt;htmlFor&lt;/code&gt; with a matching input &lt;code&gt;id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the input an accessible name. Clicking the label also focuses the input. In plain HTML, the attribute is &lt;code&gt;for&lt;/code&gt;; React uses &lt;code&gt;htmlFor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wrapping an input inside its label is another valid approach. The point is to create the association, not to add &lt;code&gt;htmlFor&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;p&gt;Also, remember that a placeholder is a hint. It should never replace a visible label.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Will IDs stay unique when components are reused?
&lt;/h3&gt;

&lt;p&gt;A hardcoded &lt;code&gt;id="email"&lt;/code&gt; can work for one field. But what happens when the same component appears twice on the same screen?&lt;/p&gt;

&lt;p&gt;Duplicate IDs break label associations and assistive technology references. For reusable React components, React's &lt;code&gt;useId&lt;/code&gt; hook ensures instance-level uniqueness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;EmailField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each instance gets its own unique ID for the label connection.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; &lt;code&gt;useId&lt;/code&gt; is for accessibility relationships. List keys should come from your underlying data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Do icon-only buttons have accessible names?
&lt;/h3&gt;

&lt;p&gt;A magnifying glass icon may clearly mean “Search” to someone looking at the screen. Make sure the button also provides a programmatic name for screen readers, as well as a visual cue for sighted users who might find the icon ambiguous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;circle&lt;/span&gt; &lt;span class="na"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"m15 15 6 6"&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;aria-label&lt;/code&gt; supplies the accessible name, while &lt;code&gt;aria-hidden="true"&lt;/code&gt; hides the decorative SVG to avoid redundant announcements. Adding &lt;code&gt;title&lt;/code&gt; or a tooltip gives sighted mouse and keyboard users clarity on hover and focus.&lt;/p&gt;

&lt;p&gt;If a button already contains visible text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save changes&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not need an additional &lt;code&gt;aria-label&lt;/code&gt;. Prefer visible text whenever the design allows.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Are button types intentional?
&lt;/h3&gt;

&lt;p&gt;Inside a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;, an ordinary &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; without an explicit &lt;code&gt;type&lt;/code&gt; defaults to &lt;code&gt;type="submit"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That becomes an immediate issue when its purpose is “Cancel” or “Show password.” Make the intention explicit on every button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onCancel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Cancel
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Create account
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review every secondary button inside a form. A single missing &lt;code&gt;type="button"&lt;/code&gt; attribute can trigger accidental form submissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Are we using the right HTML elements?
&lt;/h3&gt;

&lt;p&gt;A clickable &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; can look identical to a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;openSettings&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Settings&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; does not provide native button keyboard handling, focusability, or form integration. Adding &lt;code&gt;role="button"&lt;/code&gt; without implementing &lt;code&gt;Enter&lt;/code&gt; and &lt;code&gt;Space&lt;/code&gt; key handlers still leaves keyboard users stranded.&lt;/p&gt;

&lt;p&gt;Use native semantic elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* For an action */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;openSettings&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Settings
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* For navigation */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/settings"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Settings&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Native HTML elements give you focus management, accessibility traits, and keyboard operability for free before you write a single line of extra script.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Can someone use the UI with a keyboard?
&lt;/h3&gt;

&lt;p&gt;Set your mouse aside and test the page using only &lt;code&gt;Tab&lt;/code&gt;, &lt;code&gt;Shift + Tab&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Space&lt;/code&gt;, and &lt;code&gt;Escape&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you clearly see which element currently has focus?&lt;/li&gt;
&lt;li&gt;Can you open, navigate, and close custom dropdowns and dialogs?&lt;/li&gt;
&lt;li&gt;When a modal opens, does focus move inside and stay trapped within it?&lt;/li&gt;
&lt;li&gt;Does focus return to the trigger element when the modal closes?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When styling focus states in CSS, prefer &lt;code&gt;:focus-visible&lt;/code&gt; over &lt;code&gt;:focus&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Better: Shows strong outline only during keyboard navigation */&lt;/span&gt;
&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#2563eb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never set &lt;code&gt;outline: none&lt;/code&gt; without providing a prominent, high-contrast &lt;code&gt;:focus-visible&lt;/code&gt; replacement.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Are validation errors connected and useful?
&lt;/h3&gt;

&lt;p&gt;A red border alone does not explain the issue to users. An error message should identify what went wrong and be programmatically tied to the field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UsernameField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-error`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Username&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;
        &lt;span class="na"&gt;aria-invalid&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;aria-describedby&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;errorId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-invalid&lt;/code&gt; flags the field's state to assistive technology.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-describedby&lt;/code&gt; associates the error text with the input so it reads aloud when the field receives focus.&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;role="alert"&lt;/code&gt; or using &lt;code&gt;aria-live="polite"&lt;/code&gt; ensures dynamically rendered client-side errors are announced immediately, even if the input is already focused.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. What happens while data loads or when a request fails?
&lt;/h3&gt;

&lt;p&gt;Review more than just the successful response.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it obvious that a network request is running?&lt;/li&gt;
&lt;li&gt;Can a user accidentally submit multiple times?&lt;/li&gt;
&lt;li&gt;Does a failed submission preserve the user's filled-in data?&lt;/li&gt;
&lt;li&gt;If a search input fires rapid requests, can an older response overwrite newer results?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When indicating submission progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;aria-busy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Saving…&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save changes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on disabled buttons:&lt;/strong&gt; Adding &lt;code&gt;disabled&lt;/code&gt; directly to a focused button can cause screen readers to immediately drop focus back to the top of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. For critical flows, consider keeping the button focusable with &lt;code&gt;aria-disabled="true"&lt;/code&gt; while ignoring pointer and click events in your handler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  9. Does the layout survive real content?
&lt;/h3&gt;

&lt;p&gt;AI prompts and sample mocks often feature convenient, short text. Real production data is messy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test extreme character lengths, such as very long email addresses, multiline error messages, and long URLs.&lt;/li&gt;
&lt;li&gt;Test localized strings that might expand by 30% or more.&lt;/li&gt;
&lt;li&gt;Test zoom up to 200% to ensure text does not clip or overflow out of containers.&lt;/li&gt;
&lt;li&gt;For images, ensure non-decorative images have descriptive &lt;code&gt;alt&lt;/code&gt; text, while decorative illustrations use &lt;code&gt;alt=""&lt;/code&gt; to avoid cluttering screen readers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before merging: quick checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist during code review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Inputs have programmatically connected labels (&lt;code&gt;htmlFor&lt;/code&gt; / &lt;code&gt;useId&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;[ ] Generated IDs remain unique when components repeat.&lt;/li&gt;
&lt;li&gt;[ ] Icon-only controls have accessible names (&lt;code&gt;aria-label&lt;/code&gt;) and visual tooltips.&lt;/li&gt;
&lt;li&gt;[ ] Buttons have explicit &lt;code&gt;type="button"&lt;/code&gt; or &lt;code&gt;type="submit"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Interactive actions use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, while navigation uses &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Keyboard navigation works smoothly with visible &lt;code&gt;:focus-visible&lt;/code&gt; indicators.&lt;/li&gt;
&lt;li&gt;[ ] Field errors use &lt;code&gt;aria-invalid&lt;/code&gt; and &lt;code&gt;aria-describedby&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Loading, disabled, empty, and failure states are explicitly handled.&lt;/li&gt;
&lt;li&gt;[ ] Layout holds up under browser zoom and long, unexpected text strings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Turn the checklist into agent instructions
&lt;/h2&gt;

&lt;p&gt;This checklist does not have to remain a manual reference. You can also include it in your coding agent’s review instructions.&lt;/p&gt;

&lt;p&gt;Ask the agent to check generated UI for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connected labels and unique IDs&lt;/li&gt;
&lt;li&gt;Accessible names for icon-only controls&lt;/li&gt;
&lt;li&gt;Explicit button types&lt;/li&gt;
&lt;li&gt;Native semantic elements&lt;/li&gt;
&lt;li&gt;Keyboard and focus handling&lt;/li&gt;
&lt;li&gt;Programmatically connected validation errors&lt;/li&gt;
&lt;li&gt;Loading, empty, disabled, and failure states&lt;/li&gt;
&lt;li&gt;Long content, zoom, and responsive-layout issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent can catch many code-level problems before a pull request reaches manual review. However, it cannot fully reproduce how the interface feels with a keyboard, screen reader, browser zoom, or real production content. Use agent review as an additional review layer, then verify the important interactions manually.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>react</category>
    </item>
    <item>
      <title>Uniqueness Pre-Checks vs. Database Constraints: Why You Need Both</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Wed, 02 Sep 2026 02:55:08 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/uniqueness-pre-checks-vs-database-constraints-why-you-need-both-48j0</link>
      <guid>https://dev.to/janarthanan_soundararajan/uniqueness-pre-checks-vs-database-constraints-why-you-need-both-48j0</guid>
      <description>&lt;p&gt;Many applications need to make sure that a value is unique.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Usernames&lt;/li&gt;
&lt;li&gt;Phone numbers&lt;/li&gt;
&lt;li&gt;Product slugs&lt;/li&gt;
&lt;li&gt;Invitation codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common approach is to check whether the value already exists before saving it.&lt;/p&gt;

&lt;p&gt;That check is useful, but it is not enough.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A uniqueness pre-check improves UX.&lt;br&gt;&lt;br&gt;
A unique database constraint protects data integrity.&lt;br&gt;&lt;br&gt;
You usually want both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The uniqueness pre-check
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4dujgavvp9qi65gj7hi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4dujgavvp9qi65gj7hi.jpg" alt="The uniqueness pre-check" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Suppose a user is registering with an email address.&lt;/p&gt;

&lt;p&gt;Before creating the account, the application can check whether the email already exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This email is already registered.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the application to show a clear message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This email cannot be used.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is more helpful than showing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something went wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pre-check improves the user experience because it explains the problem early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; Be careful with email availability messages. Saying “This email is already registered” can reveal whether someone has an account. In sensitive flows, use a more general message, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“This email cannot be used.”&lt;/li&gt;
&lt;li&gt;“If an account exists for this email, you will receive further instructions.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  But a pre-check is not a guarantee
&lt;/h2&gt;

&lt;p&gt;A pre-check asks the database a question first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does this email already exist?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in a separate operation, the application creates the record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create the user.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a small gap between these two operations.&lt;/p&gt;

&lt;p&gt;During that gap, another request can create the same value.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple race-condition example
&lt;/h2&gt;

&lt;p&gt;Imagine two people submit the same username at almost the same time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A: Is "alex" available? → Yes
Request B: Is "alex" available? → Yes

Request A: Create "alex" → Success
Request B: Create "alex" → Success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the database does not enforce uniqueness, both requests may succeed.&lt;/p&gt;

&lt;p&gt;This is called a race condition.&lt;/p&gt;

&lt;p&gt;The username was available when both requests checked it. But both requests checked it before either request saved it.&lt;/p&gt;

&lt;p&gt;This is also known as a TOCTOU problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time Of Check
        ↓
Time Of Use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of the check can become outdated before the actual write happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database constraint
&lt;/h2&gt;

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

&lt;p&gt;A unique database constraint makes the database responsible for enforcing the rule.&lt;/p&gt;

&lt;p&gt;For example, in Prisma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
  id       String @id @default(cuid())
  username String @unique
  email    String @unique
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database guarantees that two users cannot have the same username or email address.&lt;/p&gt;

&lt;p&gt;Even if two requests arrive at the same time, only one can successfully create the record.&lt;/p&gt;

&lt;p&gt;The other request will fail.&lt;/p&gt;

&lt;p&gt;This protects the data even when the write comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A web form&lt;/li&gt;
&lt;li&gt;An API client&lt;/li&gt;
&lt;li&gt;A background job&lt;/li&gt;
&lt;li&gt;An admin tool&lt;/li&gt;
&lt;li&gt;A migration&lt;/li&gt;
&lt;li&gt;A webhook&lt;/li&gt;
&lt;li&gt;A script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database enforces the rule for every write path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the database error
&lt;/h2&gt;

&lt;p&gt;The database constraint protects the data, but users should not see a raw database error.&lt;/p&gt;

&lt;p&gt;The server should catch the unique-constraint error and return a friendly response.&lt;/p&gt;

&lt;p&gt;With Prisma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrismaClientKnownRequestError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;P2002&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;That username or email is already in use.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pre-check gives an early explanation in normal situations.&lt;/p&gt;

&lt;p&gt;The database constraint handles the situations that the pre-check cannot guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not use only the database constraint?
&lt;/h2&gt;

&lt;p&gt;You can rely only on the database constraint for data integrity.&lt;/p&gt;

&lt;p&gt;Your data will still be safe.&lt;/p&gt;

&lt;p&gt;However, the user experience may be less helpful.&lt;/p&gt;

&lt;p&gt;The user might submit a form and receive an error only after the request finishes.&lt;/p&gt;

&lt;p&gt;A pre-check can provide earlier feedback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This username is already taken.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is better than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unique constraint violation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why not use only the pre-check?
&lt;/h2&gt;

&lt;p&gt;A pre-check alone cannot protect your data.&lt;/p&gt;

&lt;p&gt;It can fail because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two requests can arrive at the same time.&lt;/li&gt;
&lt;li&gt;A different API may skip the check.&lt;/li&gt;
&lt;li&gt;A background job may write directly to the database.&lt;/li&gt;
&lt;li&gt;A bug may bypass the application validation.&lt;/li&gt;
&lt;li&gt;A malicious client may send requests directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Application-level checks are useful, but they are not the final authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  The recommended flow
&lt;/h2&gt;

&lt;p&gt;A robust implementation usually follows this flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User submits the form
        ↓
Application performs a pre-check
        ↓
If the value exists, show a friendly message
        ↓
If it appears available, attempt the insert
        ↓
Database constraint enforces uniqueness
        ↓
If a race occurs, catch the error
        ↓
Show a friendly message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pre-check and the database constraint are not competing solutions.&lt;/p&gt;

&lt;p&gt;They solve different problems:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application pre-check&lt;/td&gt;
&lt;td&gt;Gives users early and helpful feedback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database constraint&lt;/td&gt;
&lt;td&gt;Guarantees that duplicate data cannot be stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error handling&lt;/td&gt;
&lt;td&gt;Converts conflicts into a friendly response&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  One detail about checking while typing
&lt;/h2&gt;

&lt;p&gt;Some applications check username availability while the user is typing.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alex
alex_
alex_d
alex_de
alex_dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can create many database requests.&lt;/p&gt;

&lt;p&gt;If you use this approach, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debouncing the request&lt;/li&gt;
&lt;li&gt;Checking only after a minimum number of characters&lt;/li&gt;
&lt;li&gt;Rate-limiting the endpoint&lt;/li&gt;
&lt;li&gt;Checking again when the form is submitted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Availability shown in the UI can become outdated.&lt;/p&gt;

&lt;p&gt;The final database write must still rely on the unique constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;A uniqueness pre-check is helpful, but it is only a preview of the current state.&lt;/p&gt;

&lt;p&gt;A unique database constraint is the actual rule.&lt;/p&gt;

&lt;p&gt;The pre-check improves the user experience.&lt;/p&gt;

&lt;p&gt;The constraint protects the truth of your data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pre-checks communicate.&lt;br&gt;&lt;br&gt;
Constraints enforce.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For reliable applications, use the pre-check to guide users, use the database constraint to protect your data, and handle the final conflict gracefully.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>backend</category>
      <category>database</category>
    </item>
    <item>
      <title>Monorepo, Turborepo, Modular Monolith, and Microservices: What Actually Changes?</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Tue, 25 Aug 2026 18:08:36 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/monorepo-turborepo-modular-monolith-and-microservices-what-actually-changes-29kl</link>
      <guid>https://dev.to/janarthanan_soundararajan/monorepo-turborepo-modular-monolith-and-microservices-what-actually-changes-29kl</guid>
      <description>&lt;p&gt;A Turborepo can contain multiple applications under &lt;code&gt;apps/&lt;/code&gt;. If those applications can be built and deployed separately, does that mean the system uses microservices?&lt;/p&gt;

&lt;p&gt;Not necessarily. Multiple applications in one repository tell us how the source code is organized. Microservices are defined by runtime and operational boundaries.&lt;/p&gt;

&lt;p&gt;Consider this small structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform/
├── apps/
│   ├── marketing/
│   └── app/
└── packages/
    └── db/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is this a monorepo, a modular monolith, or a microservices architecture?&lt;/p&gt;

&lt;p&gt;It is clearly a monorepo. It might contain a modular-monolith application. The tree alone, however, cannot prove that any microservices exist.&lt;/p&gt;

&lt;p&gt;While formalizing the architecture of SlotSyncro, a portfolio application, I wanted to sharpen how I distinguished these familiar concepts. They are often discussed together, even though each describes a different layer of a system. Placing them at their correct layers turns a broad understanding into a more precise architectural model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four concepts at four different layers
&lt;/h2&gt;

&lt;p&gt;Here is the central distinction:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it describes&lt;/th&gt;
&lt;th&gt;Primary question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monorepo&lt;/td&gt;
&lt;td&gt;Repository organization&lt;/td&gt;
&lt;td&gt;Where does the code live?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turborepo&lt;/td&gt;
&lt;td&gt;Task orchestration&lt;/td&gt;
&lt;td&gt;How are builds, tests, and other tasks coordinated?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modular monolith&lt;/td&gt;
&lt;td&gt;Internal application design&lt;/td&gt;
&lt;td&gt;How is one deployable application divided?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microservices&lt;/td&gt;
&lt;td&gt;Runtime architecture&lt;/td&gt;
&lt;td&gt;How are services deployed, scaled, and connected?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Turbopack is related by name, but it solves another problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository organization → Monorepo
Task orchestration      → Turborepo
Application structure   → Modular monolith
Runtime architecture    → Microservices
Compilation             → Turbopack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turborepo coordinates tasks across workspaces. Turbopack bundles a Next.js application. These concepts do not occupy the same architectural layer, so they are not direct alternatives.&lt;/p&gt;

&lt;p&gt;That is why questions such as “Should I use a monorepo or microservices?” are difficult to answer as stated. A team can use both, either, or neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes SlotSyncro a monorepo?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;monorepo&lt;/strong&gt; is one repository that contains multiple related projects. Those projects might be applications, shared libraries, configuration packages, or services. They share Git history and can be changed together, but they can still have separate dependencies, scripts, and release processes.&lt;/p&gt;

&lt;p&gt;SlotSyncro currently has this workspace structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;slotsyncro/
├── apps/
│   ├── marketing/    # Public marketing website
│   └── app/          # Scheduling product
├── packages/
│   └── db/           # Prisma and database infrastructure
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The examples in this article are based on SlotSyncro, a scheduling product I am creating as a portfolio application. The repository is publicly viewable as a practical implementation reference: &lt;a href="https://github.com/TechAaroorian/slotsyncro" rel="noopener noreferrer"&gt;SlotSyncro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a monorepo because multiple identifiable projects live in one Git repository. The applications and database package have their own &lt;code&gt;package.json&lt;/code&gt; files, while pnpm workspaces connect them. A change to the product and its database package can be reviewed and committed together.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;workspace&lt;/strong&gt; is one project recognized and managed by the repository's package manager. In this example, &lt;code&gt;apps/app&lt;/code&gt;, &lt;code&gt;apps/marketing&lt;/code&gt;, and &lt;code&gt;packages/db&lt;/code&gt; are separate pnpm workspaces. Each can define its own package name, dependencies, and commands, while using another local workspace as a dependency without publishing it to a package registry.&lt;/p&gt;

&lt;p&gt;The monorepo label does not tell us how many production processes exist. It does not mean every folder is independently deployed, every application is a microservice, or every shared file needs its own package.&lt;/p&gt;

&lt;p&gt;Turborepo is not required either. A repository can be a monorepo using only pnpm, npm, or Yarn workspaces. Turborepo is an additional task runner, not the feature that makes it a monorepo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Turborepo actually does
&lt;/h2&gt;

&lt;p&gt;In this repository, the tools sit roughly in this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git repository
      ↓
pnpm workspaces
      ↓
Turborepo
      ↓
Next.js, Prisma, Vitest, and other workspace tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root commands delegate work across the repository. Typical Turborepo commands look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;turbo run build
turbo run &lt;span class="nb"&gt;test
&lt;/span&gt;turbo run lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turborepo understands workspace dependencies, schedules tasks in the right order, caches successful outputs, avoids repeated work, and can filter execution to selected workspaces. For example, a task graph can ensure that database-client generation runs before dependent application builds.&lt;/p&gt;

&lt;p&gt;None of this provides service-to-service communication. Turborepo does not create API gateways, message queues, independent databases, distributed transactions, container orchestration, or production scaling rules.&lt;/p&gt;

&lt;p&gt;Turborepo knows that one workspace depends on another. It does not decide whether those workspaces become separate production services.&lt;/p&gt;

&lt;p&gt;In current Next.js versions, &lt;a href="https://nextjs.org/docs/app/getting-started/installation" rel="noopener noreferrer"&gt;Turbopack is the default bundler&lt;/a&gt; for development and production builds unless Webpack is explicitly selected. The similar names can make the tools sound interchangeable, but their scopes are different: Turborepo operates on the repository's task graph, while Turbopack operates on an application's module graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  A modular monolith inside a monorepo
&lt;/h2&gt;

&lt;p&gt;Now move one level inward, from the repository to &lt;code&gt;apps/app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;modular monolith&lt;/strong&gt; is one deployable application organized into distinct business modules. The modules have defined responsibilities and controlled ways to interact, but they remain part of the same application boundary instead of becoming independently operated network services. It keeps the operational simplicity of a monolith while making internal ownership and dependencies clearer.&lt;/p&gt;

&lt;p&gt;SlotSyncro’s current product code is still mainly grouped by technical concerns such as routes, actions, components, and utilities. The accepted direction is to evolve it incrementally into business modules. A possible target shape is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/app/
├── app/                       # Next.js routes
└── modules/                   # Proposed structure
    ├── identity/
    ├── availability/
    ├── booking/
    ├── polls/
    ├── meetings/
    └── notifications/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These would be business modules, not microservices. They would have clear responsibilities and controlled interfaces, but remain part of the same Next.js application, deploy together, and use one PostgreSQL database.&lt;/p&gt;

&lt;p&gt;The runtime remains simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monorepo
├── Marketing application
└── Scheduling application ───────── one deployment boundary
    ├── Identity module
    ├── Availability module
    ├── Booking module
    ├── Polls module
    └── Notifications module
                 ↓
             PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The boundaries are architectural, but they are not network boundaries. A booking operation can call availability logic as a typed function and update related state in one local database transaction.&lt;/p&gt;

&lt;p&gt;That is the useful middle ground of a modular monolith: explicit ownership without immediately accepting the operational cost of a distributed system. A monolith is not synonymous with unstructured legacy code. It can have deliberate, well-tested internal boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Folder names are not module boundaries
&lt;/h2&gt;

&lt;p&gt;A booking module might eventually use a small internal structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modules/booking/
├── domain/
│   ├── booking.ts
│   └── booking-rules.ts
├── application/
│   ├── create-booking.ts
│   └── cancel-booking.ts
├── infrastructure/
│   └── booking-repository.ts
├── ui/
│   └── booking-form.tsx
└── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact folders matter less than the ownership they express. The module’s &lt;code&gt;index.ts&lt;/code&gt; can act as its public API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBooking&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/modules/booking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code outside the module should avoid reaching through that API into implementation details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Avoid reaching into another module's internals.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bookingRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/modules/booking/infrastructure/booking-repository&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Folders help people see the intended boundary, but they do not enforce it. Public APIs, code review, dependency rules, and tests make the boundary real. Modular-monolith architecture depends more on enforced boundaries than on folder names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why multiple applications still do not mean microservices
&lt;/h2&gt;

&lt;p&gt;Return to the original two applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
├── marketing/
└── app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;marketing&lt;/code&gt; presents public product information. &lt;code&gt;app&lt;/code&gt; owns authentication, availability, bookings, polls, and other scheduling workflows. They are different user-facing surfaces, and the repository can build them independently.&lt;/p&gt;

&lt;p&gt;That still says nothing about the product’s internal business capabilities being independently operated services. Its booking, polls, and notification concerns currently belong to the same product application. No internal network call is required merely because the repository contains two Next.js apps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Multiple applications” is a repository observation. “Microservices” is a runtime conclusion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even independent deployment of the marketing and product sites would not make either one a microservice architecture. They may simply be two deployable web applications with different purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What would actually create microservices?
&lt;/h2&gt;

&lt;p&gt;Imagine the repository later changed to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform/
├── apps/
│   ├── marketing/
│   ├── web/
│   ├── scheduling-service/
│   ├── billing-service/
│   ├── notification-service/
│   └── background-worker/
└── packages/
    ├── contracts/
    ├── observability/
    └── config/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The names suggest microservices, but the folders still do not prove it. These components become services through meaningful operational independence: they run as separate processes, deploy and scale independently, communicate through APIs or messages, isolate at least some failures, and have intentionally defined business and data ownership.&lt;/p&gt;

&lt;p&gt;A simplified runtime might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────┐
│   Web application   │  Deployment A
└──────────┬──────────┘
           │ HTTPS
┌──────────▼──────────┐
│ Scheduling service │  Deployment B
└──────────┬──────────┘
           │ MeetingScheduled event
┌──────────▼──────────┐
│Notification service│  Deployment C
└─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the boundaries are also network and deployment boundaries. That creates capabilities such as independent scaling and release ownership, but it also introduces timeouts, retries, message delivery, distributed tracing, versioned contracts, and consistency decisions.&lt;/p&gt;

&lt;p&gt;The trade-off is easier to see side by side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Modular monolith&lt;/th&gt;
&lt;th&gt;Microservices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Modules call one another in-process&lt;/td&gt;
&lt;td&gt;Services communicate over a network or queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usually deployed together&lt;/td&gt;
&lt;td&gt;Independently deployable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easier cross-module transactions&lt;/td&gt;
&lt;td&gt;Distributed consistency must be handled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One primary runtime unit&lt;/td&gt;
&lt;td&gt;Multiple runtime units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower operational complexity&lt;/td&gt;
&lt;td&gt;Higher operational complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boundaries enforced in code&lt;/td&gt;
&lt;td&gt;Boundaries also enforced by the network&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Independent deployment is important, but it is not the whole definition. If several processes must always release together, share unclear data ownership, and cannot fail independently, the system may be a distributed monolith instead.&lt;/p&gt;

&lt;p&gt;Microservices are not the inevitable final stage of a successful modular monolith. For SlotSyncro’s current scale and requirements, local calls and transactions are valuable, while independent service operation has not earned its cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turborepo supports either runtime architecture
&lt;/h2&gt;

&lt;p&gt;Turborepo can coordinate a modular-monolith repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/marketing
apps/app
packages/db
packages/ui       # possible future extraction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also coordinate a repository containing services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/web
apps/scheduling-service
apps/notification-service
apps/worker
packages/contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both cases, it can run the complete build or target a workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;turbo run build
turbo run build &lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;scheduling-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second repository may use microservices because of how those applications run, communicate, and deploy—not because Turborepo launched their build tasks. Turborepo can manage a repository containing microservices, but it does not turn applications into microservices.&lt;/p&gt;

&lt;p&gt;A monorepo can therefore contain a marketing application, a modular-monolith product, and several genuine services at the same time. Repository boundaries and runtime boundaries do not need to match one-to-one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep dependency direction clear
&lt;/h2&gt;

&lt;p&gt;One general monorepo rule prevents a great deal of confusion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Applications may import packages.
Packages must not import applications.
Good: apps/app → packages/db
Bad:  packages/db → apps/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;packages/db&lt;/code&gt; imports application authentication code, the dependency becomes circular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/app → packages/db → apps/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package is no longer reusable infrastructure; it secretly depends on one consumer. Ownership becomes unclear, and isolated testing becomes harder.&lt;/p&gt;

&lt;p&gt;For example, database infrastructure should not retrieve application-specific session context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad: packages/db knows about an application's authentication.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCurrentUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../apps/app/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, the database package can expose infrastructure without importing from an application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/db&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The booking module can own its product-specific query while depending on that infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/app/modules/booking/infrastructure/booking-repository.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@slotsyncro/db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findBookings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application layer supplies the authentication context it owns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/app/modules/booking/application/list-my-bookings.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCurrentUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findBookings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This principle applies to monorepos generally. Turborepo can understand a dependency graph, but it cannot decide whether that graph represents sensible ownership.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should a module become a package?
&lt;/h2&gt;

&lt;p&gt;Once developers see a &lt;code&gt;packages/&lt;/code&gt; directory, it is tempting to move every cleanly named capability into it. That usually creates boundaries before there is evidence for them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application module&lt;/th&gt;
&lt;th&gt;Shared package&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Belongs to one application&lt;/td&gt;
&lt;td&gt;Lives outside a specific application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Represents a product capability&lt;/td&gt;
&lt;td&gt;Provides a reusable capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May depend on application conventions&lt;/td&gt;
&lt;td&gt;Should expose a stable interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usually has one consumer&lt;/td&gt;
&lt;td&gt;Often has multiple consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploys with its application&lt;/td&gt;
&lt;td&gt;May be consumed by several applications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, SlotSyncro should begin with booking behavior close to its only consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/app/modules/booking/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should not create &lt;code&gt;packages/booking&lt;/code&gt; merely to make the repository tree look more sophisticated. Keeping code inside its only consumer is not poor architecture. It is often the clearest expression of ownership.&lt;/p&gt;

&lt;p&gt;Possible future extractions include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/
├── domain/    # Framework-independent scheduling rules
├── email/     # Shared templates or rendering infrastructure
└── ui/        # Components genuinely shared by both applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are proposals, not current SlotSyncro workspaces. &lt;code&gt;packages/domain&lt;/code&gt; becomes useful if scheduling calculations need to run in the product, a worker, and isolated tests. &lt;code&gt;packages/email&lt;/code&gt; becomes useful after multiple notification workflows need common rendering. &lt;code&gt;packages/ui&lt;/code&gt; becomes especially useful when the marketing and product applications share a maintained design system, or when the component system has meaningful independent development and testing value.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical extraction test
&lt;/h2&gt;

&lt;p&gt;A package should have at least one strong justification, and preferably more than one.&lt;/p&gt;

&lt;h3&gt;
  
  
  More than one consumer
&lt;/h3&gt;

&lt;p&gt;Two applications or runtimes need the same capability. Reuse is real, not predicted from one similar-looking component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Framework-independent domain value
&lt;/h3&gt;

&lt;p&gt;Scheduling calculations can run without React, Next.js, Prisma, or a provider SDK. That isolation makes the code easier to reuse and reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stable inputs and outputs
&lt;/h3&gt;

&lt;p&gt;The capability exposes a small, intentional API instead of importing application internals. If its interface changes with every feature, extraction may be premature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Independent testing value
&lt;/h3&gt;

&lt;p&gt;Its behavior can be tested meaningfully without starting the complete application. This is especially useful for scheduling, timezone, or consensus rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Different build or deployment concerns
&lt;/h3&gt;

&lt;p&gt;Database generation, email rendering, shared contracts, or worker code may need distinct tasks. A workspace boundary can make those workflows explicit.&lt;/p&gt;

&lt;p&gt;Do not extract code merely because &lt;code&gt;packages/&lt;/code&gt; makes the repository look more sophisticated. Extraction should clarify ownership or enable a real workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision framework
&lt;/h2&gt;

&lt;p&gt;When the terminology starts to blur, I return to these questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do multiple related projects need to live together?
→ Consider a monorepo.
Do you need caching and coordinated workspace tasks?
→ Consider Turborepo.
Does one deployable application need stronger internal boundaries?
→ Use a modular monolith.
Does one capability require independent deployment, scaling,
failure isolation, data ownership, or team autonomy?
→ Consider extracting a service.
Is some code used by only one application?
→ Keep it close to that application for now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sequence avoids using a runtime solution for a repository problem—or creating a network boundary when a code boundary is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository note:&lt;/strong&gt; SlotSyncro is a public portfolio and reference repository, not an open-source project or starter template. Some modules and packages discussed in this article are architectural proposals and do not exist in the current implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete mental model
&lt;/h2&gt;

&lt;p&gt;A monorepo organizes where code lives.&lt;/p&gt;

&lt;p&gt;A modular monolith organizes boundaries inside one application.&lt;/p&gt;

&lt;p&gt;Microservices define independently operated runtime boundaries.&lt;/p&gt;

&lt;p&gt;Turborepo coordinates tasks across the repository.&lt;/p&gt;

&lt;p&gt;Turbopack compiles a Next.js application.&lt;/p&gt;

&lt;p&gt;The number of folders under &lt;code&gt;apps/&lt;/code&gt; does not determine the runtime architecture. Deployment, communication, scaling, and ownership boundaries do.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>monorepo</category>
      <category>turborepo</category>
    </item>
    <item>
      <title>I thought Vite broke my 3D physics. It actually taught me how tree-shaking really works.</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Sun, 16 Aug 2026 09:05:54 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/i-thought-vite-broke-my-3d-physics-it-actually-taught-me-how-tree-shaking-really-works-30oj</link>
      <guid>https://dev.to/janarthanan_soundararajan/i-thought-vite-broke-my-3d-physics-it-actually-taught-me-how-tree-shaking-really-works-30oj</guid>
      <description>&lt;p&gt;In modern browser-based game development, every kilobyte matters. Larger JavaScript payloads increase download, parse, compile, and execution times, which can delay interactivity—especially on slower devices or networks.&lt;/p&gt;

&lt;p&gt;To enforce a strict, lightweight architecture for our latest project at &lt;strong&gt;techaaroorian-games&lt;/strong&gt;, we utilized Babylon's ES module packages (&lt;code&gt;@babylonjs/core&lt;/code&gt;). For a modular application, this structure gives the bundler as much opportunity as possible to remove code the application doesn't actively use.&lt;/p&gt;

&lt;p&gt;We set up Vite, TypeScript, Babylon.js, and the Havok WebAssembly physics engine.&lt;/p&gt;

&lt;p&gt;Then, the engine crashed.&lt;br&gt;
&lt;em&gt;Error: `No Physics Engine available.&lt;/em&gt;`&lt;/p&gt;

&lt;p&gt;Here is the debugging story of why our physics vanished, and what it teaches us about modern web bundlers.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The Simple Tree-Shaking Model
&lt;/h3&gt;

&lt;p&gt;To understand why my game crashed, I had to look at how modern bundlers view our code. Vite uses &lt;strong&gt;Rolldown&lt;/strong&gt; for its production builds—it doesn't read our minds; it reads the module graph.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is the code reachable and needed?&lt;/strong&gt; → Keep it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can the bundler safely determine it's unnecessary?&lt;/strong&gt; → Remove it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what happens when a module doesn't export an API you need to call, but instead contains &lt;em&gt;module-level registration code&lt;/em&gt; you need to run?&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The Wasm Trap
&lt;/h3&gt;

&lt;p&gt;My first hurdle wasn't even tree-shaking; it was dependency optimization. My terminal completely froze when I tried to start the development server.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@babylonjs/havok&lt;/code&gt; ships JavaScript alongside WebAssembly assets, and in my setup, Vite's dependency optimization stalled while processing the package.&lt;/p&gt;

&lt;p&gt;I fixed it by telling Vite not to pre-bundle that dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;optimizeDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babylonjs/havok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This lets Vite handle the package outside the dependency pre-bundling step.&lt;/p&gt;

&lt;p&gt;With the server finally running, I expected to see my 3D sphere bouncing with gravity. Instead, I got a blank canvas and the fatal error: &lt;strong&gt;&lt;code&gt;No Physics Engine available.&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The "Aha!" Moment: Vite Didn't Break My Physics
&lt;/h3&gt;

&lt;p&gt;Vite didn't break my physics. &lt;strong&gt;It exposed an architectural design I didn't know existed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To keep base payloads lightweight, highly optimized frameworks separate their pure implementation logic from their &lt;em&gt;runtime registration&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Because I only imported the core &lt;code&gt;Scene&lt;/code&gt; and &lt;code&gt;Mesh&lt;/code&gt; builders, I never directly referenced the physics module's exported API. My import graph didn't establish a need for that registration module to execute. Because its runtime registration wasn't reachable through the imports I had written, the production build could treat it as unnecessary.&lt;/p&gt;

&lt;p&gt;The physics code wasn't missing because it was broken. It was missing because its only purpose was to execute registration code, and I didn't tell my bundler that mattered!&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Fix: Side-Effect Imports
&lt;/h3&gt;

&lt;p&gt;To fix this, I had to learn the difference between asking a bundler for an &lt;em&gt;API&lt;/em&gt; versus asking it for an &lt;em&gt;action&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Normal Import:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Mesh&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babylonjs/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Translation: "I need to use the Mesh API."&lt;/span&gt;
&lt;span class="c1"&gt;// Bundler trace: API is used, preserve the module.&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Side-Effect Import:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babylonjs/core/Physics/physicsEngineComponent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Translation: "I don't need an exported API from this module, but I do need its module-level registration code to execute."&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;By adding that single side-effect import, the module executes immediately upon loading. It performs the registration necessary for Babylon's physics functionality to be available at runtime, while still allowing the bundler to aggressively tree-shake everything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85gfxz8zz52x5uml8tqu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85gfxz8zz52x5uml8tqu.gif" alt="Marble in Canvas" width="799" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Big Lesson
&lt;/h3&gt;

&lt;p&gt;As I continue building out techaaroorian-games, this completely changed how I look at build tools.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static dependency&lt;/strong&gt; → Bundler can trace it → &lt;strong&gt;Keep what's reachable&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module-level side effect&lt;/strong&gt; → Execution itself matters → &lt;strong&gt;Make that dependency explicit&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tree-shaking isn't magic. It's static analysis. And once I understood that, "unused code" became a much more interesting concept. The bundler isn't trying to understand what my game means; it is analyzing what my module graph tells it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I am documenting my whole journey learning WebGPU and game dev. You can clone the full working engine setup we built today in the &lt;a href="https://github.com/techaaroorian-games/techaaroorian-games" rel="noopener noreferrer"&gt;techaaroorian-games GitHub repository&lt;/a&gt;!)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>vite</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>The DOM Is Lying to Your Types: 3 Subtle React + Zod Validation Traps</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Thu, 13 Aug 2026 06:38:47 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/the-dom-is-lying-to-your-types-3-subtle-react-zod-validation-traps-2a0p</link>
      <guid>https://dev.to/janarthanan_soundararajan/the-dom-is-lying-to-your-types-3-subtle-react-zod-validation-traps-2a0p</guid>
      <description>&lt;p&gt;A common production stack for React forms is &lt;strong&gt;React Hook Form&lt;/strong&gt; for client-side state, &lt;strong&gt;Zod&lt;/strong&gt; for schema validation, and &lt;strong&gt;Next.js Server Actions&lt;/strong&gt; for the backend mutation.&lt;/p&gt;

&lt;p&gt;It feels incredibly robust. But beneath these excellent tools, a subtle friction point is waiting to corrupt your data: &lt;strong&gt;Native form values are transport-oriented, not domain-oriented.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prefer a visual explanation? &lt;a href="https://youtu.be/eyE8JeGp2CI?si=s9s4yoNKM4BeuvC1" rel="noopener noreferrer"&gt;https://youtu.be/eyE8JeGp2CI?si=s9s4yoNKM4BeuvC1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even when the browser exposes typed helpers like &lt;code&gt;input.valueAsNumber&lt;/code&gt; or &lt;code&gt;input.checked&lt;/code&gt;, the raw values crossing a form boundary rarely match your application's domain model directly. React Hook Form can help normalize some primitives at registration (e.g., &lt;code&gt;{ valueAsNumber: true }&lt;/code&gt;), but this doesn't eliminate the need for boundary validation: &lt;code&gt;NaN&lt;/code&gt;, empty values, optional semantics, and unexpected server-side inputs still require explicit handling.&lt;/p&gt;

&lt;p&gt;When engineers rush to bridge the gap between transport data and strict TypeScript domain logic, it’s incredibly easy to implement "quick fixes" (like Zod's &lt;code&gt;z.coerce&lt;/code&gt;) that make the TypeScript compiler happy but introduce silent data corruption.&lt;/p&gt;

&lt;p&gt;To build robust systems, we need to internalize a core architectural principle:&lt;br&gt;
&lt;strong&gt;Validation ≠ Type Conversion ≠ Normalization.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look at the modern form pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DOM
 ↓
React Hook Form / FormData (Transport Representation)
 ↓
Normalization (Where "30" becomes 30)
 ↓
Validation (Does 30 meet our business rules?)
 ↓
Domain Value (Strictly Typed)
 ↓
Persistence (Database)

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

&lt;/div&gt;



&lt;p&gt;Whether you pull data from React Hook Form state or native &lt;code&gt;FormData&lt;/code&gt;, your transport representation is limited. It contains strings and &lt;code&gt;File&lt;/code&gt; objects, while missing fields are represented by the absolute absence of an entry.&lt;/p&gt;

&lt;p&gt;As engineers, our golden rule at the normalization boundary should be: &lt;strong&gt;Don't ask "How do I make Zod accept this?" Ask "What does this external value actually mean in my domain?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are three subtle data-boundary traps in the React/Zod ecosystem that happen when those responsibilities blur, and how to design reliable pipelines to prevent them.&lt;/p&gt;




&lt;h3&gt;
  
  
  Trap 1: The Number Coercion "Zero-Bypass" (Semantic Loss)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Scenario:&lt;/strong&gt; You are building a scheduling app and have an optional "Buffer Time" input (&lt;code&gt;&amp;lt;input type="number" name="buffer" /&amp;gt;&lt;/code&gt;). Because the DOM sends a string, &lt;code&gt;z.number().optional()&lt;/code&gt; fails. To fix it, you reach for Zod's coercion API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The Trap&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bufferTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Actual Problem:&lt;/strong&gt; This isn't just a type error; it's semantic data loss.&lt;br&gt;
In JavaScript, &lt;code&gt;Number("")&lt;/code&gt; evaluates to &lt;code&gt;0&lt;/code&gt;. If a user leaves the input blank because they &lt;em&gt;don't want a buffer&lt;/em&gt;, Zod intercepts the empty string, coerces it to &lt;code&gt;0&lt;/code&gt;, and validates it. Because &lt;code&gt;0&lt;/code&gt; is a valid number, the &lt;code&gt;.optional()&lt;/code&gt; check is bypassed entirely.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;User Intent&lt;/th&gt;
&lt;th&gt;DOM Input&lt;/th&gt;
&lt;th&gt;Naive Coercion&lt;/th&gt;
&lt;th&gt;Real Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No buffer configured&lt;/td&gt;
&lt;td&gt;&lt;code&gt;""&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Semantic Loss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit 0 minutes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"0"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You just erased the semantic distinction between "not provided" (&lt;code&gt;undefined&lt;/code&gt;) and "explicitly provided as zero minutes" (&lt;code&gt;0&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Define explicit normalization semantics using &lt;code&gt;z.preprocess()&lt;/code&gt; before Zod attempts to validate. Crucially, if the user submits garbage data (like &lt;code&gt;"banana"&lt;/code&gt;), we must not silently swallow it—we pass it through so &lt;code&gt;z.number()&lt;/code&gt; can properly reject it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Explicit Normalization&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionalNumberSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preprocess&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Preserve the semantic meaning of "empty"&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Safely attempt conversion&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Pass bad data through&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; 

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Note: Normalization answers "What value is this?" by intentionally accepting JavaScript's flexible numeric string syntax, like &lt;code&gt;"+30"&lt;/code&gt;. If your domain requires stricter constraints—for example, whole integers only—you enforce that as a **Validation&lt;/em&gt;* rule: &lt;code&gt;z.number().int().min(0)&lt;/code&gt;).*&lt;/p&gt;




&lt;h3&gt;
  
  
  Trap 2: The Boolean Checkbox Nightmare
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Scenario:&lt;/strong&gt; You have a checkbox for an event setting (&lt;code&gt;&amp;lt;input type="checkbox" name="isPrivate" /&amp;gt;&lt;/code&gt;). You grab the value from React Hook Form or &lt;code&gt;FormData&lt;/code&gt; and pass it to Zod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The Trap&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;isPrivate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Actual Problem:&lt;/strong&gt; Native HTML checkboxes do not submit &lt;code&gt;false&lt;/code&gt; when unchecked; the field is omitted entirely. Calling &lt;code&gt;formData.get("isPrivate")&lt;/code&gt; therefore returns &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Furthermore, if you are passing JSON payloads from a client UI, passing the string &lt;code&gt;"false"&lt;/code&gt; through &lt;code&gt;Boolean("false")&lt;/code&gt; actually evaluates to &lt;code&gt;true&lt;/code&gt; (because it's a non-empty string). Zod's &lt;code&gt;z.coerce.boolean()&lt;/code&gt; is blind to your domain intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Don't use arbitrary form strings for boolean coercion. Normalize the transport representation explicitly, and—just like with numbers—don't silently swallow invalid data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Explicit Normalization&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkboxSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;isPrivate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preprocess&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Recognize explicit truthy transport values&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Recognize explicit falsy transport values&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Let unexpected values pass through so z.boolean() can catch and reject them!&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This guarantees that expected inputs map cleanly to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;, but if a bad actor or a bug sends &lt;code&gt;"banana"&lt;/code&gt;, it reaches &lt;code&gt;z.boolean()&lt;/code&gt; and gets properly rejected. &lt;strong&gt;Unknown ≠ false.&lt;/strong&gt; Normalization should not silently destroy information.&lt;/p&gt;




&lt;h3&gt;
  
  
  Trap 3: The Timezone Date Parse (Ambiguous Instants)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Scenario:&lt;/strong&gt; A user selects a date and time for an event using &lt;code&gt;&amp;lt;input type="datetime-local" /&amp;gt;&lt;/code&gt;. It outputs a string like &lt;code&gt;"2026-08-15T09:00"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The Trap&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Actual Problem:&lt;/strong&gt; A JavaScript &lt;code&gt;Date&lt;/code&gt; object represents an &lt;em&gt;absolute instant&lt;/em&gt; in time (milliseconds since the epoch). But a user's selection of "August 15, 09:00" is a &lt;em&gt;local wall-clock time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If a user in New York submits &lt;code&gt;"2026-08-15T09:00"&lt;/code&gt; and the server interprets the value using its own runtime timezone, the application has created an absolute instant without knowing which timezone the user intended. You've created an ambiguous point in time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: This is one of the exact problems the JavaScript &lt;code&gt;Temporal&lt;/code&gt; proposal is designed to model explicitly: separating a &lt;code&gt;PlainDateTime&lt;/code&gt; from a &lt;code&gt;ZonedDateTime&lt;/code&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Never parse localized date strings natively in your schema without explicitly capturing and applying the timezone context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ The Fix&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Accept the raw wall-clock time string (e.g., "2026-08-15T09:00")&lt;/span&gt;
  &lt;span class="na"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{4}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;T&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d{2}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// Accept the user's explicit timezone (e.g., "America/New_York")&lt;/span&gt;
  &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Explicitly combine the wall-clock time and timezone &lt;/span&gt;
  &lt;span class="c1"&gt;// into an absolute UTC instant before saving to the database&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;absoluteUtcInstant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;convertToUtcInstant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Takeaway
&lt;/h3&gt;

&lt;p&gt;Validation isn't just about rejecting invalid input. At application boundaries, it's also where we define how external, untyped representations officially become typed domain values.&lt;/p&gt;

&lt;p&gt;By explicitly separating normalization from validation—and paying attention to semantic zero-bypasses, truthy strings, and wall-clock ambiguity—you can build resilient systems that prevent data corruption before it ever reaches your database.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Vite Asked Me to Choose Between Oxlint and ESLint</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Sat, 08 Aug 2026 10:39:44 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/why-vite-asked-me-to-choose-between-oxlint-and-eslint-3ihl</link>
      <guid>https://dev.to/janarthanan_soundararajan/why-vite-asked-me-to-choose-between-oxlint-and-eslint-3ihl</guid>
      <description>&lt;p&gt;While initializing a new Vite project (&lt;code&gt;npx create-vite&lt;/code&gt;), I hit an interactive CLI prompt that paused my standard workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;◆ Which linter to use?
  ● Oxlint
  ○ ESLint

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

&lt;/div&gt;



&lt;p&gt;Having relied on ESLint as my default linter for years, this piqued my curiosity. The filled bullet (&lt;code&gt;●&lt;/code&gt;) wasn't a formal endorsement from Vite—it was simply where the CLI cursor landed—but it made me pause. &lt;strong&gt;Why is Vite offering Oxlint alongside ESLint, and when does it make sense to use it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are my first impressions after looking into what Oxlint is, how it works under the hood, and how I plan to test it out.&lt;/p&gt;




&lt;h2&gt;
  
  
  ❓ Why Is Vite Offering Oxlint?
&lt;/h2&gt;

&lt;p&gt;Vite doesn't recommend Oxlint over ESLint—it simply presents both as first-class options during project creation. Given the growing adoption of native tooling such as SWC, Biome, and Rolldown, adding Oxlint feels consistent with the broader direction of the frontend ecosystem, while still leaving the choice to developers.&lt;/p&gt;

&lt;p&gt;Rather than forcing one choice, Vite simply lets developers decide which workflow best fits their needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 What is Oxlint?
&lt;/h2&gt;

&lt;p&gt;From my preliminary research, &lt;strong&gt;Oxlint&lt;/strong&gt; is a linter developed as part of the &lt;strong&gt;Oxc&lt;/strong&gt; (Oxidized Compiler) project.&lt;/p&gt;

&lt;p&gt;The core difference seems to be the engine: while ESLint runs on Node.js/JavaScript, Oxlint is written in &lt;strong&gt;Rust&lt;/strong&gt;. Because Oxlint is a native executable rather than a JavaScript package running inside Node.js, it can reduce startup overhead and take advantage of parallel execution on modern CPUs.&lt;/p&gt;

&lt;p&gt;Rather than trying to replace every custom rule or plugin in the JavaScript ecosystem on day one, Oxlint's current focus appears to be catching &lt;strong&gt;common correctness bugs, React hook pitfalls, and type safety issues&lt;/strong&gt; with minimal setup overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚔️ How They Seem to Compare
&lt;/h2&gt;

&lt;p&gt;Here is a high-level snapshot based on the documentation and early community feedback I've gathered so far:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric / Feature&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ESLint&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Oxlint&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js / JavaScript&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Rust (Oxidized)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Varies by project size, plugins, and custom rules&lt;/td&gt;
&lt;td&gt;Official Oxlint benchmarks report significantly faster execution than ESLint, and early community feedback has generally reflected similar experiences, though results depend on the project.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup &amp;amp; Config&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires &lt;code&gt;eslint.config.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Designed around sensible zero-config defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plugin Ecosystem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very mature and extensive&lt;/td&gt;
&lt;td&gt;Smaller plugin ecosystem, with built-in support for many common React, TypeScript, JSX, and Jest rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep customization &amp;amp; strict team policies&lt;/td&gt;
&lt;td&gt;Fast local feedback and low initial setup friction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚡ Why the Speed Pitch Caught My Eye
&lt;/h2&gt;

&lt;p&gt;What made me want to investigate Oxlint further is the promise of shorter developer feedback loops.&lt;/p&gt;

&lt;p&gt;In past projects, running pre-commit hooks (&lt;code&gt;husky&lt;/code&gt; / &lt;code&gt;lint-staged&lt;/code&gt;) or saving files during local hot reloading (HMR) sometimes created a small delay while validating code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Typical local feedback loop:&lt;/span&gt;
&lt;span class="c"&gt;# 1. Save or stage a file.&lt;/span&gt;
&lt;span class="c"&gt;# 2. Run the linting process.&lt;/span&gt;
&lt;span class="c"&gt;# 3. Wait for rule validation before moving on.&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;If Rust-based linters can make that validation step feel nearly instant during active coding, that could be a subtle but nice improvement in daily flow. I'm curious to see if that holds true as my project grows from a fresh scaffold into a complete application.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ The Hybrid Idea I'm Curious to Try
&lt;/h2&gt;

&lt;p&gt;As I read through discussions on how developers are approaching this, one approach I came across repeatedly was &lt;strong&gt;a hybrid setup&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For local dev &amp;amp; pre-commit hooks:&lt;/strong&gt; Use &lt;strong&gt;Oxlint&lt;/strong&gt; as a lightweight frontline defender to catch common mistakes instantly without slowing down commits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For CI/CD pipelines:&lt;/strong&gt; Keep &lt;strong&gt;ESLint&lt;/strong&gt; running in GitHub Actions if complex, custom ESLint plugins or team-wide policies are required later on.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Conceptual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hybrid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;setup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;experiment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Frontline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;HMR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pre-commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hooks&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:fast"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oxlint --deny-warnings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Deep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structural&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;audit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CI/CD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pipelines&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint ."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 Next Steps &amp;amp; What I'm Testing Next
&lt;/h2&gt;

&lt;p&gt;I decided to select &lt;strong&gt;Oxlint&lt;/strong&gt; for this new Vite build as an initial experiment. Since this is a brand-new project, it felt like a good opportunity to evaluate a newer tool without worrying about migrating an existing lint configuration.&lt;/p&gt;

&lt;p&gt;Right now, the codebase is in its early stages, so getting started required virtually no configuration. As I build out components, integrate state management, and add visualizations (like D3 charts), I'm planning to observe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How well Oxlint catches real React/TypeScript errors during active development.&lt;/li&gt;
&lt;li&gt;Whether I run into any rule limitations that force me to bring ESLint back into the loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'll be keeping notes as the project develops and might share a follow-up post with actual hands-on impressions once the site is fully built!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; If my experience changes after building a larger React application, I'll publish a follow-up comparing Oxlint and ESLint with real-world usage rather than first impressions.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>oxlint</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fixing Vitest CI Worker Crashes by Switching from jsdom to happy-dom</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Fri, 07 Aug 2026 10:43:45 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/fixing-vitest-ci-worker-crashes-by-switching-from-jsdom-to-happy-dom-1h95</link>
      <guid>https://dev.to/janarthanan_soundararajan/fixing-vitest-ci-worker-crashes-by-switching-from-jsdom-to-happy-dom-1h95</guid>
      <description>&lt;p&gt;Our Vitest suite passed every time locally, but GitHub Actions failed before executing a single test. Instead of failed assertions, every test worker crashed during startup, leaving us with 0% coverage and a broken CI pipeline.&lt;/p&gt;

&lt;p&gt;After narrowing the issue down to our test environment, we replaced &lt;code&gt;jsdom&lt;/code&gt; with &lt;code&gt;happy-dom&lt;/code&gt;. That change resolved the worker crashes in our environment and restored a stable GitHub Actions pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment Context
&lt;/h2&gt;

&lt;p&gt;To help determine if this applies to your setup, here is the stack where we encountered this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Next.js (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; Vitest, &lt;code&gt;@testing-library/react&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package Manager:&lt;/strong&gt; &lt;code&gt;pnpm&lt;/code&gt; (Workspace / Monorepo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI Environment:&lt;/strong&gt; GitHub Actions (&lt;code&gt;ubuntu-latest&lt;/code&gt;, Node.js 20)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. The Error: Unhandled Worker Failures in CI
&lt;/h2&gt;

&lt;p&gt;During the &lt;code&gt;pnpm --filter app test:coverage&lt;/code&gt; step in GitHub Actions, Vitest failed immediately upon initializing the worker pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vitest caught 7 unhandled errors during the test run.

⎯⎯⎯⎯⎯⎯ Unhandled Error ⎯⎯⎯⎯⎯⎯⎯
Error: [vitest-pool]: Failed to start forks worker for test files /apps/app/__tests__/poll.test.ts.

Caused by: TypeError: webidl.util.markAsUncloneable is not a function
 ❯ new CacheStorage ../node_modules/jsdom/node_modules/undici/lib/web/cache/cachestorage.js:20:17
 ❯ Object.&amp;lt;anonymous&amp;gt; ../node_modules/jsdom/node_modules/undici/index.js:179:25
 ❯ Object.&amp;lt;anonymous&amp;gt; ../node_modules/jsdom/lib/api.js:12:33

 Test Files  no tests
      Tests  no tests
     Errors  7 errors

ERROR: Coverage for lines (0%) does not meet global threshold (80%)

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

&lt;/div&gt;



&lt;p&gt;If you encounter errors such as &lt;code&gt;webidl.util.markAsUncloneable is not a function&lt;/code&gt; or &lt;code&gt;Failed to start forks worker&lt;/code&gt; in a &lt;code&gt;vitest-pool&lt;/code&gt; setup, the problem may lie in your test environment's dependency tree rather than in your test code.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Investigation
&lt;/h2&gt;

&lt;p&gt;At first, we assumed one of the test files was failing or throwing an uncaught exception.&lt;/p&gt;

&lt;p&gt;But the stack trace told a different story. None of the 7 test files had actually started executing. Every failure occurred while Vitest was initializing worker processes, which meant the problem was happening before our application code even loaded.&lt;/p&gt;

&lt;p&gt;The stack trace pointed directly into &lt;code&gt;jsdom&lt;/code&gt;'s internal &lt;code&gt;undici&lt;/code&gt; dependency during worker initialization (&lt;code&gt;jsdom/node_modules/undici/lib/web/cache/cachestorage.js&lt;/code&gt;). Rather than a bug in our application code, this suggested an environment-level compatibility issue involving the test environment rather than our application code.&lt;/p&gt;

&lt;p&gt;Beyond initial loading issues, &lt;code&gt;jsdom&lt;/code&gt; emulates a large portion of standard browser behavior and maintains a detailed DOM object model. In resource-constrained CI environments (like standard Linux runners in GitHub Actions), this extra memory footprint can increase resource usage and startup time, especially during coverage runs.&lt;/p&gt;

&lt;p&gt;We could have spent more time investigating the exact dependency mismatch, but since our tests only required standard DOM APIs, switching to a lighter implementation was the simpler and lower-risk solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Switch: Moving to &lt;code&gt;happy-dom&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;happy-dom&lt;/code&gt; Worked
&lt;/h3&gt;

&lt;p&gt;Unlike &lt;code&gt;jsdom&lt;/code&gt;, &lt;code&gt;happy-dom&lt;/code&gt; focuses specifically on implementing the browser APIs commonly needed by modern frontend unit tests (such as React synthetic events and DOM queries) rather than reproducing every full browser behavior. Because of its smaller scope, &lt;code&gt;happy-dom&lt;/code&gt; generally starts faster and has a lower resource footprint than &lt;code&gt;jsdom&lt;/code&gt; for many component-testing scenarios, making it a good fit for React component testing in Vitest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Replace Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; app remove jsdom
pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; app add &lt;span class="nt"&gt;-D&lt;/span&gt; happy-dom @vitest/coverage-v8

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Update Vitest Configuration
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;vitest.config.ts&lt;/code&gt; to use &lt;code&gt;happy-dom&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./vitest.setup.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json-summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.next/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest.config.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest.setup.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.d.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Recommended Test Setup
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;vitest.setup.ts&lt;/code&gt;, ensure component trees are explicitly unmounted after each test block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/jest-dom/vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cleanup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afterEach&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: This cleanup setup was not required to fix the worker initialization issue, but we keep it to ensure each test runs with a clean DOM and to avoid state leaking between tests.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (&lt;code&gt;jsdom&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;After (&lt;code&gt;happy-dom&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Worker Initialization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Failed across all 7 test files&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;All workers started successfully&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Coverage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ 0% (Threshold failure)&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Coverage generated successfully&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pipeline Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ CI failed consistently&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;CI passed consistently&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Failed during worker initialization&lt;/td&gt;
&lt;td&gt;⚡ &lt;strong&gt;Entire suite finished in ~15 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While we didn't identify the exact package combination that triggered the incompatibility, replacing &lt;code&gt;jsdom&lt;/code&gt; removed the issue entirely for our CI environment, making further investigation unnecessary for our use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. When Should You Keep &lt;code&gt;jsdom&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Every tool comes with trade-offs, and &lt;code&gt;happy-dom&lt;/code&gt; isn't a 1:1 replacement for every project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep &lt;code&gt;jsdom&lt;/code&gt; if:&lt;/strong&gt; Your tests depend on advanced browser APIs that &lt;code&gt;happy-dom&lt;/code&gt; doesn't fully implement, or you are testing behavior that closely mirrors browser layout and parsing internals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;happy-dom&lt;/code&gt; if:&lt;/strong&gt; You are primarily running React, Vue, or Svelte component tests with &lt;code&gt;@testing-library&lt;/code&gt;, where rendering components and asserting on DOM output is all you need.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Lessons Learned &amp;amp; Takeaways
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; Before changing test code, check whether the failure occurs before the first test executes. If worker initialization fails, the root cause is often the test environment, dependency graph, or runtime—not your application logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The lesson wasn't that &lt;code&gt;happy-dom&lt;/code&gt; is universally better than &lt;code&gt;jsdom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It was that your test environment should match your testing needs. For our React component tests, a lightweight DOM implementation was enough—and choosing the simplest test environment that satisfied our requirements made the CI pipeline both faster and more reliable.&lt;/p&gt;

&lt;p&gt;Sometimes the quickest path to a stable CI pipeline isn't finding the perfect root cause—it's choosing the simplest tool that satisfies your testing requirements.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>cicd</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Should You Unit Test Glue Files? A Practical Decision Matrix</title>
      <dc:creator>Janarthanan Soundararajan (Jana)</dc:creator>
      <pubDate>Thu, 06 Aug 2026 05:57:22 +0000</pubDate>
      <link>https://dev.to/janarthanan_soundararajan/should-you-unit-test-glue-files-a-practical-decision-matrix-1k0b</link>
      <guid>https://dev.to/janarthanan_soundararajan/should-you-unit-test-glue-files-a-practical-decision-matrix-1k0b</guid>
      <description>&lt;p&gt;Have you ever spent an entire afternoon writing a unit test for a five-line setup component?&lt;/p&gt;

&lt;p&gt;You mock three context providers, an &lt;code&gt;Auth.js&lt;/code&gt; session hook, and a router adapter—only to assert that a button renders in the DOM. At that point, it’s worth asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are we testing our application, or just testing our mocks?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In React applications, not every file benefits equally from isolated unit tests—but figuring out which ones those are isn't always obvious. Over time, I’ve moved away from chasing high coverage numbers for its own sake toward a more pragmatic framework for balancing unit tests (with &lt;strong&gt;Vitest&lt;/strong&gt; or &lt;strong&gt;Jest&lt;/strong&gt;) and End-to-End integration tests (with &lt;strong&gt;Playwright&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Here is how I think about testing wiring code without grinding developer velocity to a halt.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are "Glue Files"?
&lt;/h2&gt;

&lt;p&gt;"Glue files" isn't an official software engineering term. It's simply the name I use for files whose primary responsibility is connecting parts of an application rather than implementing business logic.&lt;/p&gt;

&lt;p&gt;Most React developers will recognize these files instantly in their codebases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry &amp;amp; Wiring Files:&lt;/strong&gt; &lt;code&gt;main.tsx&lt;/code&gt;, &lt;code&gt;App.tsx&lt;/code&gt;, &lt;code&gt;providers.tsx&lt;/code&gt;, &lt;code&gt;layout.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing &amp;amp; Client Config:&lt;/strong&gt; &lt;code&gt;router.tsx&lt;/code&gt;, &lt;code&gt;queryClient.ts&lt;/code&gt;, &lt;code&gt;middleware.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store Initialization:&lt;/strong&gt; &lt;code&gt;store.ts&lt;/code&gt; (when primarily configuring Redux/Zustand rather than implementing custom middleware or complex state logic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth &amp;amp; Localization Wrappers:&lt;/strong&gt; &lt;code&gt;&amp;lt;SignInButton/&amp;gt;&lt;/code&gt; wrappers around &lt;code&gt;Auth.js&lt;/code&gt; or Firebase, and language switchers wrapping &lt;code&gt;next-intl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Primitives:&lt;/strong&gt; Base design system elements (&lt;code&gt;components/ui/*&lt;/code&gt; built over Radix UI or Tailwind primitives)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These files act as the "mortar" holding your external dependencies, global state trees, and visual layout together.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Cost of Unit Testing Pure Glue Files
&lt;/h2&gt;

&lt;p&gt;Writing unit tests for pure domain functions is fast and rewarding. Writing unit tests for glue files often leads to three common headaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 90% Mock Setup, 10% Actual Assertion
&lt;/h3&gt;

&lt;p&gt;To unit test a file that simply instantiates a client or wires up a third-party hook, you often end up writing dozens of lines of mock setup code just to test a few lines of JSX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/AuthButton.test.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Mocking external library hooks and routers&lt;/span&gt;
&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-auth/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;useSession&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unauthenticated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders sign-in button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignInButton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;When 90% of a test file consists of &lt;code&gt;vi.mock()&lt;/code&gt;, you aren't really testing application behavior—you are mostly testing your ability to write mocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. High Fragility with Low Return
&lt;/h3&gt;

&lt;p&gt;Because these tests are tightly bound to the internal signatures of third-party libraries, updating a dependency often breaks your unit tests even if the actual user-facing feature works perfectly fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Testing Framework Behavior Instead of Your Logic
&lt;/h3&gt;

&lt;p&gt;Maintainers of packages like TanStack Query, Radix UI, or React Router already maintain extensive unit test suites. Writing unit tests to verify that &lt;code&gt;&amp;lt;QueryClientProvider client="{queryClient}"&amp;gt;&lt;/code&gt; passes down context means you're often verifying framework behavior rather than your own application's logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Decision Matrix: &lt;strong&gt;Does This File Make Decisions?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In my experience, asking this single question eliminates most debates about whether a file deserves unit tests: &lt;strong&gt;Does this file make decisions?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│    Does this file make decisions?   │
└─────────────────────────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
       Yes               No
        │                 │
        ▼                 ▼
┌───────────────┐   ┌────────────────────┐
│   Unit Test   │   │ Is it wiring only? │
│ Vitest / Jest │   └─────────┬──────────┘
└───────────────┘             │
                       ┌───────┴────────┐
                       │                │
                      Yes              No
                       │                │
                       ▼                ▼
             Verify Through       Re-evaluate File
             Integration / E2E    Responsibilities
              (Playwright)

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Seeing the Matrix in Practice
&lt;/h2&gt;

&lt;p&gt;Let’s look at two concrete examples to see how this decision rule works in real code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: &lt;code&gt;queryClient.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pure Wiring (Skip Unit Tests):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision Check:&lt;/strong&gt; Does this file make decisions? &lt;strong&gt;No.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy:&lt;/strong&gt; Skip unit testing. Verify data fetching behavior through Playwright integration or end-to-end smoke tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Contains Decision Logic (Unit Test It!):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision Check:&lt;/strong&gt; Does this file make decisions? &lt;strong&gt;Yes.&lt;/strong&gt; It evaluates the current environment to choose retry behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy:&lt;/strong&gt; Write a Vitest unit test to verify that retries evaluate correctly across environments.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Example 2: &lt;code&gt;router.tsx&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Static Routes (Skip Unit Tests):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision Check:&lt;/strong&gt; Does this file make decisions? &lt;strong&gt;No.&lt;/strong&gt; It's purely declarative static wiring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conditional Routing (Unit Test It!):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAppRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;userRole&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdminPanel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision Check:&lt;/strong&gt; Does this file make decisions? &lt;strong&gt;Yes.&lt;/strong&gt; It computes routes based on role permissions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick Checklist: When Should You Unit Test a Glue File?
&lt;/h2&gt;

&lt;p&gt;My default rule is: &lt;strong&gt;In most cases, I don't unit test pure glue files because they don't contain business logic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, a glue file becomes worth unit testing as soon as it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Contains branching or conditional logic (&lt;code&gt;if / else&lt;/code&gt;, ternary checks)&lt;/li&gt;
&lt;li&gt;✅ Transforms configuration dynamically at runtime&lt;/li&gt;
&lt;li&gt;✅ Chooses behavior based on environment variables&lt;/li&gt;
&lt;li&gt;✅ Applies feature flags or user permissions&lt;/li&gt;
&lt;li&gt;✅ Has custom calculation logic that can fail independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, skip the unit test and verify it at the integration or E2E level.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This isn't a rigid rule—it's a heuristic. Every codebase is different, and your testing strategy should reflect your team's priorities, risk tolerance, and architecture.&lt;/p&gt;

&lt;p&gt;Coverage tells you how much code executed. Good tests tell you how confident you should be when shipping.&lt;/p&gt;

&lt;p&gt;If there's one takeaway to keep in mind when designing your test suite, it's this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Code deserves tests in proportion to the decisions it makes.&lt;br&gt;
Business logic makes decisions.&lt;br&gt;
Glue code makes connections.&lt;br&gt;
Test them accordingly.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>nextjs</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
