<?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: KhaledSalem</title>
    <description>The latest articles on DEV Community by KhaledSalem (@khaledmsalem).</description>
    <link>https://dev.to/khaledmsalem</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%2F3565357%2F72721ed3-cc00-44d0-89e6-86b75ca3f59c.png</url>
      <title>DEV Community: KhaledSalem</title>
      <link>https://dev.to/khaledmsalem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khaledmsalem"/>
    <language>en</language>
    <item>
      <title>When Does Local State Become Shared State? A React State Ownership Guide</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sat, 24 Oct 2026 20:24:57 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/when-does-local-state-become-shared-state-a-react-state-ownership-guide-1fi5</link>
      <guid>https://dev.to/khaledmsalem/when-does-local-state-become-shared-state-a-react-state-ownership-guide-1fi5</guid>
      <description>&lt;p&gt;&lt;em&gt;State Management Decision Authority — Part 02&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your React state doesn't need to be global just because two components need it.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;In Part 1, &lt;a href="https://react.dev/learn/managing-state" rel="noopener noreferrer"&gt;useState vs useReducer: Stop Choosing Tools Before Understanding State&lt;/a&gt;, I explored an architectural idea I call the &lt;strong&gt;State Management Decision Authority&lt;/strong&gt;: choosing state management strategies through consistent criteria rather than tool preferences.&lt;/p&gt;

&lt;p&gt;One of those criteria deserves a deeper discussion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State Ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because sharing state and globalizing state are two very different decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Checkout Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a checkout feature with three components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Checkout&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="nc"&gt;ShippingForm&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;OrderSummary&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;PaymentSection&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;Each component has a different responsibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ShippingForm&lt;/code&gt; collects the delivery address.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OrderSummary&lt;/code&gt; displays the shipping cost.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PaymentSection&lt;/code&gt; checks whether checkout requirements are satisfied.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they all depend on some of the same information.&lt;/p&gt;

&lt;p&gt;If the user changes the shipping country, the available shipping methods might change. The order summary may need to recalculate its totals, and payment might need to be disabled until a valid shipping option is selected.&lt;/p&gt;

&lt;p&gt;The three components now need to coordinate.&lt;/p&gt;

&lt;p&gt;Does that mean we should introduce a global store?&lt;/p&gt;

&lt;p&gt;Not yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. First Principle: Ownership Before Accessibility
&lt;/h2&gt;

&lt;p&gt;Before choosing a tool, ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should own this state?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is different from asking who needs to read it.&lt;/p&gt;

&lt;p&gt;A component may consume state without owning its lifecycle or controlling its transitions.&lt;/p&gt;

&lt;p&gt;For our checkout example, the checkout feature itself is a reasonable ownership boundary.&lt;/p&gt;

&lt;p&gt;We can lift state to the nearest common ancestor:&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;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;function&lt;/span&gt; &lt;span class="nf"&gt;Checkout&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;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCountry&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="s2"&gt;EG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ShippingForm&lt;/span&gt;
        &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onCountryChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setCountry&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;OrderSummary&lt;/span&gt; &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;country&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;PaymentSection&lt;/span&gt; &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;country&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;Now we have a single source of truth for &lt;code&gt;country&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No Zustand. No Redux. No global Context provider.&lt;/p&gt;

&lt;p&gt;Just state ownership aligned with the component tree.&lt;/p&gt;

&lt;p&gt;Of course, the example is intentionally simplified. A real checkout flow would also need shipping options, validation rules, and order totals. Those can be derived or managed within the same feature boundary as appropriate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing state doesn't automatically require global ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Smallest Sufficient Ownership Boundary
&lt;/h2&gt;

&lt;p&gt;Here's a principle I find useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give each piece of state the smallest ownership boundary that satisfies its requirements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why the smallest?&lt;/p&gt;

&lt;p&gt;Because expanding the ownership boundary also expands the potential dependency surface.&lt;/p&gt;

&lt;p&gt;More components may gain access to the state. More logic may begin relying on its shape and behavior. Changes to that state can become harder to reason about.&lt;/p&gt;

&lt;p&gt;That doesn't make shared state inherently bad.&lt;/p&gt;

&lt;p&gt;It means accessibility should be intentional.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Potential ownership&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Toggle a local dropdown&lt;/td&gt;
&lt;td&gt;Dropdown component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coordinate shipping and payment&lt;/td&gt;
&lt;td&gt;Checkout feature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Share a wizard's progress across steps&lt;/td&gt;
&lt;td&gt;Wizard boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display a notification across independent features&lt;/td&gt;
&lt;td&gt;Shared notification service or store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache fetched product data&lt;/td&gt;
&lt;td&gt;Server-state cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that ownership isn't determined by the number of components alone.&lt;/p&gt;

&lt;p&gt;It's determined by the scope of coordination.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. When useReducer Still Belongs Inside a Feature
&lt;/h2&gt;

&lt;p&gt;What if our checkout state becomes more complicated?&lt;/p&gt;

&lt;p&gt;Changing the shipping country might invalidate the selected shipping method.&lt;/p&gt;

&lt;p&gt;Applying a discount might update the totals.&lt;/p&gt;

&lt;p&gt;Submitting the checkout might require a valid combination of address, shipping option, and payment information.&lt;/p&gt;

&lt;p&gt;Now we have related transitions and invariants.&lt;/p&gt;

&lt;p&gt;This might justify &lt;code&gt;useReducer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shippingMethod&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;checkoutReducer&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;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COUNTRY_CHANGED&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;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;shippingMethod&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHIPPING_SELECTED&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;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;shippingMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nl"&gt;default&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;state&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;In a production implementation, &lt;code&gt;SHIPPING_SELECTED&lt;/code&gt; should also validate that the selected shipping method is permitted for the current country, or accept only previously validated options.&lt;/p&gt;

&lt;p&gt;The important architectural detail is that &lt;strong&gt;introducing &lt;code&gt;useReducer&lt;/code&gt; changes how we manage transitions, not who needs to own the state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can continue keeping the reducer inside &lt;code&gt;Checkout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A complex state is not necessarily a global state.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. When Context Becomes Useful
&lt;/h2&gt;

&lt;p&gt;Now imagine our checkout feature grows deeper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checkout
  └── CheckoutLayout
       └── CheckoutSteps
            └── ShippingStep
                 └── ShippingForm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing checkout state through components that don't use it can become inconvenient.&lt;/p&gt;

&lt;p&gt;This is where Context might help.&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;createContext&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="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;const&lt;/span&gt; &lt;span class="nx"&gt;CheckoutContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;CheckoutProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;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;checkoutReducer&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;CheckoutContext&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;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="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;CheckoutContext&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;useCheckout&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;context&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;CheckoutContext&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;context&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="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;Missing CheckoutProvider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can place &lt;code&gt;CheckoutProvider&lt;/code&gt; around the checkout feature rather than the entire application.&lt;/p&gt;

&lt;p&gt;Context solves an accessibility problem: delivering a value to consumers deeper in a component tree.&lt;/p&gt;

&lt;p&gt;It doesn't automatically solve complex transitions, and it isn't inherently a rendering optimization.&lt;/p&gt;

&lt;p&gt;When a provider value changes, components consuming that Context are eligible to re-render. In performance-sensitive flows, we may need to split contexts, stabilize values, or use more selective subscription mechanisms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context changes how state is accessed. It doesn't require changing who owns it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. When an External Store Starts Making Sense
&lt;/h2&gt;

&lt;p&gt;Suppose the requirements evolve again.&lt;/p&gt;

&lt;p&gt;Several independent application features now need to coordinate shared client state.&lt;/p&gt;

&lt;p&gt;Maybe a shopping cart is modified from product listings, product detail pages, and a persistent cart drawer.&lt;/p&gt;

&lt;p&gt;A dedicated shared store may become easier to maintain than repeatedly lifting state or creating an increasingly broad Context.&lt;/p&gt;

&lt;p&gt;This is where Zustand or Redux Toolkit becomes worth evaluating.&lt;/p&gt;

&lt;p&gt;But the decision shouldn't be:&lt;/p&gt;

&lt;p&gt;"We have five components consuming this state, so let's use Zustand."&lt;/p&gt;

&lt;p&gt;A more useful question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we now have an ownership and subscription problem that our existing structure handles poorly?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Other considerations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether consumers need selective subscriptions&lt;/li&gt;
&lt;li&gt;Whether cross-feature updates are becoming difficult to coordinate&lt;/li&gt;
&lt;li&gt;Whether state transitions need stronger conventions or debugging capabilities&lt;/li&gt;
&lt;li&gt;Whether the project already has a suitable established state management architecture&lt;/li&gt;
&lt;li&gt;Whether the additional abstraction reduces more complexity than it introduces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An external store may be the right solution.&lt;/p&gt;

&lt;p&gt;But it should be the result of a concrete architectural requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Shared Client State Isn't Server State
&lt;/h2&gt;

&lt;p&gt;There's one more boundary worth preserving.&lt;/p&gt;

&lt;p&gt;Some data is shared because it originates from the server.&lt;/p&gt;

&lt;p&gt;Product catalogs, user records, and remote inventory are examples.&lt;/p&gt;

&lt;p&gt;That introduces concerns such as caching, stale data, refetching, invalidation, and synchronization.&lt;/p&gt;

&lt;p&gt;For those cases, a server-state solution such as TanStack Query or RTK Query may be more appropriate than manually copying remote data into a general-purpose client store.&lt;/p&gt;

&lt;p&gt;This isn't an either-or decision.&lt;/p&gt;

&lt;p&gt;A real application can use local React state for a form, a shared client store for a cart, and a server-state cache for product information.&lt;/p&gt;

&lt;p&gt;The important thing is to avoid giving one mechanism responsibilities it wasn't chosen to handle.&lt;/p&gt;

&lt;p&gt;We'll explore this distinction later in the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. A State Ownership Decision Framework
&lt;/h2&gt;

&lt;p&gt;Before changing your state architecture, try answering these questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Possible direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Does only one component need the state?&lt;/td&gt;
&lt;td&gt;Keep it local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do siblings need to coordinate?&lt;/td&gt;
&lt;td&gt;Lift to their nearest common owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is passing data through intermediate components becoming cumbersome?&lt;/td&gt;
&lt;td&gt;Consider composition or Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are related transitions becoming difficult to maintain?&lt;/td&gt;
&lt;td&gt;Consider &lt;code&gt;useReducer&lt;/code&gt; within the existing boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are independent features coordinating shared client state?&lt;/td&gt;
&lt;td&gt;Evaluate an external store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the state primarily remote data?&lt;/td&gt;
&lt;td&gt;Evaluate server-state management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The framework is deliberately not a rigid decision tree.&lt;/p&gt;

&lt;p&gt;These dimensions overlap, and different solutions can satisfy the same requirements.&lt;/p&gt;

&lt;p&gt;Its purpose is to make our reasoning explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;One of the most expensive architectural decisions is making a local concern global before it needs to be.&lt;/p&gt;

&lt;p&gt;Once unrelated features begin depending on a shared state boundary, changing that boundary can become harder.&lt;/p&gt;

&lt;p&gt;My principle is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing state is a requirement. Globalizing state is an architectural decision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the goal should be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The smallest sufficient ownership boundary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's how I'm applying the State Management Decision Authority introduced in Part 1.&lt;/p&gt;

&lt;p&gt;Not by standardizing every tool, but by standardizing how we evaluate the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unify the decision criteria, not necessarily the tools.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Part 03: Context vs Zustand vs Redux Toolkit — What Actually Justifies a Shared State Solution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We'll go beyond feature ownership and explore how subscription behavior, transition complexity, tooling, and application boundaries influence the choice.&lt;/p&gt;

&lt;p&gt;What makes you decide that React's built-in state management is no longer enough for a particular feature?&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>After one React edit, what can your build skip?</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Wed, 21 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/after-one-react-edit-what-can-your-build-skip-842</link>
      <guid>https://dev.to/khaledmsalem/after-one-react-edit-what-can-your-build-skip-842</guid>
      <description>&lt;p&gt;Compare an unchanged run with a run after a small source edit—and check the output before interpreting the timer.&lt;/p&gt;

&lt;p&gt;An unchanged build and a build after an edit answer different questions. For this walkthrough, use a small React label change that you can actually see in the built app. Keep the command and project setup the same. That gives you a more useful comparison than a single “fast build” number: what happened with no edit, and what happened after the source changed?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should a frontend developer care?
&lt;/h2&gt;

&lt;p&gt;You want to know whether the build loop improves when you are actually editing, not only when nothing changed. A visible mutation gives the comparison an output check that a timer cannot provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Keep one direct pilot command
&lt;/h2&gt;

&lt;p&gt;Use one existing &lt;a href="https://ionify.cloud/getting-started" rel="noopener noreferrer"&gt;supported app&lt;/a&gt;. Keep the team’s existing scripts, linting and tests. Add a distinct script if you need one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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="nl"&gt;"build:ionify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ionify build"&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;p&gt;Merge this entry into the existing &lt;code&gt;scripts&lt;/code&gt; object; do not replace other scripts. Record the installed package version and lockfile. From the app directory run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm run build:ionify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let the process exit and run the same command again unchanged, retaining local state and output. Use fresh processes, the same configuration and the same command for the edited run too.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Change an imported React component
&lt;/h2&gt;

&lt;p&gt;Replace the original rendered button with the edited version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&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;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"save-button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&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="c1"&gt;// After&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;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"save-button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save draft&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;These JSX fragments are illustrative, not observations. Build again. Serve the generated output with the app’s normal preview setup. For a root-page app using the default &lt;code&gt;dist&lt;/code&gt; output, one local static preview is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 4173 &lt;span class="nt"&gt;--bind&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--directory&lt;/span&gt; dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://127.0.0.1:4173/&lt;/code&gt; and inspect &lt;code&gt;#save-button&lt;/code&gt;. Expected after the edit: &lt;code&gt;Save draft&lt;/code&gt;. Record what you actually see. Adapt the directory if you intentionally selected another output path; Python’s simple server does not supply SPA route fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Name the state you compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;th&gt;Output check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Whole task restored, if applicable&lt;/td&gt;
&lt;td&gt;The task runner reused its recorded result; the engine may not have run&lt;/td&gt;
&lt;td&gt;Inspect the restored app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task executed with retained engine state&lt;/td&gt;
&lt;td&gt;The command ran; unchanged and edited runs remain separate observations&lt;/td&gt;
&lt;td&gt;Inspect the exact label after each run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fresh-state control in a separate disposable copy&lt;/td&gt;
&lt;td&gt;A different starting state, with the original fixture preserved&lt;/td&gt;
&lt;td&gt;Inspect that copy’s built app&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first row is conceptual. This walkthrough does not establish a named task-runner integration or provide invented runner commands. If your repository already has one, use its documentation to declare inputs and outputs, then verify restored versus executed status from its actual logs. If the label changed but the task remains a hit unexpectedly, preserve the evidence and investigate input declarations.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;pnpm run build:ionify&lt;/code&gt; directly bypasses that orchestration. Do not compare its elapsed boundary with a whole-task restore as if they were identical work.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Separate output evidence from interpretation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Installed version / checkout / exact command:
Mutation and retained local state:
Task outcome: restored / executed / unknown
Engine outcome: not invoked / completed / failed / unknown
Expected and observed label:
Served directory / URL / cache observations:
Elapsed boundary and observations, if measured:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A prior label experiment initially displayed an old label through same-origin HTTP 304 reuse; a fresh origin serving the retained-state output displayed the new one. Check the served directory, URL and browser reuse before concluding that generated output or engine reuse is correct or incorrect. Generated-string presence alone is not browser verification.&lt;/p&gt;

&lt;p&gt;No timing, log or internal skip count is fabricated here. Use diagnostics supported by your installed release if you need to explain the work, and report an unexpected result as carefully as a favorable one. Which edit would you use as your smallest useful check?&lt;/p&gt;

&lt;p&gt;Cover description: “One edit. Less repeat work”: an amber pencil touches one raised amber code tile among graphite tiles on an ivory background. Conceptual illustration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Conceptual illustration created with AI assistance for Ionify.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>useState vs useReducer: Stop Choosing Tools Before Understanding State</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Tue, 20 Oct 2026 20:44:28 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/usestate-vs-usereducer-stop-choosing-tools-before-understanding-state-2d8e</link>
      <guid>https://dev.to/khaledmsalem/usestate-vs-usereducer-stop-choosing-tools-before-understanding-state-2d8e</guid>
      <description>&lt;p&gt;&lt;em&gt;Why React state management needs consistent decision criteria, not another library.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;When should you use &lt;code&gt;useState&lt;/code&gt; instead of &lt;code&gt;useReducer&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The common answer is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;useState&lt;/code&gt; for simple state.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;useReducer&lt;/code&gt; for complex state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not necessarily wrong. But I think it's incomplete.&lt;/p&gt;

&lt;p&gt;Because state management complexity isn't just about how many values we store. It's also about &lt;strong&gt;who owns the state, how it changes, which rules govern those changes, and how long that state needs to live.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That leads me to a more fundamental question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do we start by choosing a state management tool before defining the criteria that should guide that choice?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Problem with Tool-First Thinking
&lt;/h2&gt;

&lt;p&gt;Imagine we're building a checkout flow in React.&lt;/p&gt;

&lt;p&gt;We have a shipping address, payment method, discount code, and several validation rules.&lt;/p&gt;

&lt;p&gt;We could use multiple &lt;code&gt;useState&lt;/code&gt; hooks. Or we could combine the logic into a &lt;code&gt;useReducer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But having multiple state variables doesn't automatically justify a reducer.&lt;/p&gt;

&lt;p&gt;What matters is how those values relate to each other.&lt;/p&gt;

&lt;p&gt;For example, changing the shipping country might invalidate the selected shipping method.&lt;/p&gt;

&lt;p&gt;Now we're no longer dealing with independent values. We're dealing with related state transitions governed by business rules.&lt;/p&gt;

&lt;p&gt;And that's where the architectural decision becomes interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Introducing a State Management Decision Authority
&lt;/h2&gt;

&lt;p&gt;What if we separated the decision-making process from the tools themselves?&lt;/p&gt;

&lt;p&gt;I like to think of this as a &lt;strong&gt;State Management Decision Authority&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's not a new React API, an additional library, or an official design pattern.&lt;/p&gt;

&lt;p&gt;It's a proposed architectural approach: define a consistent set of criteria that determines which state management strategy fits a particular problem.&lt;/p&gt;

&lt;p&gt;Before choosing a tool, evaluate five dimensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  State ownership
&lt;/h3&gt;

&lt;p&gt;Who needs access to this state?&lt;/p&gt;

&lt;p&gt;Is it owned by a single component, a feature, or multiple independent parts of the application?&lt;/p&gt;

&lt;h3&gt;
  
  
  Update complexity
&lt;/h3&gt;

&lt;p&gt;Does updating the state involve assigning a new value, or applying several related business rules?&lt;/p&gt;

&lt;h3&gt;
  
  
  State relationships
&lt;/h3&gt;

&lt;p&gt;Are state values independent, or must certain invariants remain true whenever one value changes?&lt;/p&gt;

&lt;h3&gt;
  
  
  State transitions
&lt;/h3&gt;

&lt;p&gt;Are there explicit transitions between states, such as &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;processing&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  State lifecycle
&lt;/h3&gt;

&lt;p&gt;Are we managing local UI state, shared client state, or server state that requires fetching, caching, and synchronization?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The answers should determine the tool, rather than the tool shaping how we define the problem.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. When useState Is the Right Choice
&lt;/h2&gt;

&lt;p&gt;Consider a simple toggle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsOpen&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state is local, the update is straightforward, and there are no complex transition rules.&lt;/p&gt;

&lt;p&gt;Introducing actions and a reducer here would add complexity without providing much value.&lt;/p&gt;

&lt;p&gt;The same applies to many inputs, visibility flags, and independently updated UI values.&lt;/p&gt;

&lt;p&gt;Importantly, the number of state variables alone is not a reliable decision criterion.&lt;/p&gt;

&lt;p&gt;A component with five independent state variables might be better served by five &lt;code&gt;useState&lt;/code&gt; hooks than by one reducer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal isn't to minimize the number of hooks. It's to minimize unnecessary complexity.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. When useReducer Becomes More Valuable
&lt;/h2&gt;

&lt;p&gt;Now consider a payment workflow.&lt;/p&gt;

&lt;p&gt;We have several possible states, and transitions between them must follow particular rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;paymentReducer&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;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PAYMENT_STARTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&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="kc"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PAYMENT_SUCCEEDED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&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="kc"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PAYMENT_FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&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="nl"&gt;default&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;state&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;Inside the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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;paymentReducer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of allowing different parts of the component to independently decide how the state should change, we dispatch actions describing what happened.&lt;/p&gt;

&lt;p&gt;The reducer calculates the next state.&lt;/p&gt;

&lt;p&gt;This creates a useful separation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happened vs. how the state should respond.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But a reducer doesn't automatically guarantee correctness.&lt;/p&gt;

&lt;p&gt;We still need to define valid transitions, protect state invariants, and keep side effects outside the reducer.&lt;/p&gt;

&lt;p&gt;In this example, initiating an actual payment request is a side effect, not something the reducer should perform.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Decision Authority vs. Transition Authority
&lt;/h2&gt;

&lt;p&gt;There's an important distinction here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision Authority&lt;/strong&gt; determines which state management approach we should use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State Transition Authority&lt;/strong&gt; determines how state changes are evaluated and applied.&lt;/p&gt;

&lt;p&gt;A reducer can centralize transition logic within a particular state boundary.&lt;/p&gt;

&lt;p&gt;But that doesn't mean every state variable needs a reducer, or that every reducer needs a full state machine.&lt;/p&gt;

&lt;p&gt;The architectural objective isn't to centralize everything.&lt;/p&gt;

&lt;p&gt;It's to make decisions consistently while preserving the simplest appropriate implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Beyond React Hooks: Zustand, Redux Toolkit, and Server State
&lt;/h2&gt;

&lt;p&gt;The same decision criteria can extend beyond &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useReducer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When multiple components need the same state, we can first consider lifting state up or using Context.&lt;/p&gt;

&lt;p&gt;When multiple features need shared client state, tools such as Zustand or Redux Toolkit may become appropriate.&lt;/p&gt;

&lt;p&gt;If the primary challenge involves remote data fetching, caching, invalidation, and synchronization, a dedicated server-state solution such as TanStack Query or RTK Query can be more suitable.&lt;/p&gt;

&lt;p&gt;These tools aren't necessarily mutually exclusive.&lt;/p&gt;

&lt;p&gt;An application might use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useState&lt;/code&gt; for local UI interactions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useReducer&lt;/code&gt; for a complex form workflow.&lt;/li&gt;
&lt;li&gt;Zustand or Redux Toolkit for shared client state.&lt;/li&gt;
&lt;li&gt;TanStack Query or RTK Query for server-state management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decision depends on the responsibility of each state boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A Practical Decision Framework
&lt;/h2&gt;

&lt;p&gt;Here's a lightweight framework I would use before making a state management decision.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Possible direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is the state local with simple, independent updates?&lt;/td&gt;
&lt;td&gt;Start with &lt;code&gt;useState&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are transitions governed by related rules?&lt;/td&gt;
&lt;td&gt;Consider &lt;code&gt;useReducer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is state shared within a component subtree?&lt;/td&gt;
&lt;td&gt;Consider lifting state or Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do independent features require shared client state?&lt;/td&gt;
&lt;td&gt;Evaluate Zustand or Redux Toolkit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the state primarily represent remote server data?&lt;/td&gt;
&lt;td&gt;Evaluate TanStack Query or RTK Query&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This isn't a rigid rules engine.&lt;/p&gt;

&lt;p&gt;A shared state doesn't automatically require a global store, and a complex state doesn't automatically require &lt;code&gt;useReducer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's a framework for asking better questions before committing to an implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The Bigger Architectural Principle
&lt;/h2&gt;

&lt;p&gt;State management discussions often become comparisons between libraries.&lt;/p&gt;

&lt;p&gt;Which is faster?&lt;/p&gt;

&lt;p&gt;Which has less boilerplate?&lt;/p&gt;

&lt;p&gt;Which is easier to learn?&lt;/p&gt;

&lt;p&gt;Those questions matter, but they're secondary to understanding the problem.&lt;/p&gt;

&lt;p&gt;I believe we should first establish consistent criteria for choosing a state management strategy.&lt;/p&gt;

&lt;p&gt;Then evaluate available tools against those criteria.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unify the decision criteria, not necessarily the tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the architectural principle I'm exploring here.&lt;/p&gt;

&lt;p&gt;The real skill isn't knowing every state management library.&lt;/p&gt;

&lt;p&gt;It's understanding &lt;strong&gt;why, when, and where&lt;/strong&gt; each one belongs.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useReducer&lt;/code&gt; aren't competing solutions where one must win.&lt;/p&gt;

&lt;p&gt;They're different approaches to managing state, each valuable in the right context.&lt;/p&gt;

&lt;p&gt;And the same reasoning applies when we move toward Context, Zustand, Redux Toolkit, or server-state solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let the problem define the tool, not the tool define the solution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear how other developers approach these decisions.&lt;/p&gt;

&lt;p&gt;Do you follow explicit state management criteria in your projects, or do team preferences and existing architecture usually drive the choice?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 1 of the State Management Decision Authority series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Keep useful build work after closing the terminal</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Mon, 19 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/keep-useful-build-work-after-closing-the-terminal-3cai</link>
      <guid>https://dev.to/khaledmsalem/keep-useful-build-work-after-closing-the-terminal-3cai</guid>
      <description>&lt;p&gt;A small Ionify walkthrough: run a build, let the process exit, then return to the same app without deleting its local state.&lt;/p&gt;

&lt;p&gt;You shouldn’t have to leave a terminal running just to preserve useful build work. In an existing, supported Ionify project, start with two separate invocations of the same build command. Keep the local state between them. Then change a visible label and check the built app. The point is practical: distinguish work that survives a process restart from work that exists only while a process is alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should a frontend developer care?
&lt;/h2&gt;

&lt;p&gt;Closing a terminal should not make you lose the opportunity to reuse valid local work. This exercise gives you a small way to evaluate that boundary without replacing your team’s workflow or claiming a remote-cache result.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Build an unchanged baseline
&lt;/h2&gt;

&lt;p&gt;Use an existing app configured according to the &lt;a href="https://ionify.cloud/getting-started" rel="noopener noreferrer"&gt;supported setup&lt;/a&gt;. Record the installed Ionify version, checkout revision, lockfile and exact command. This is not a standalone installation recipe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;ionify build
&lt;span class="c"&gt;# Let this process exit. Keep the project's local state.&lt;/span&gt;
pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;ionify build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run each invocation to completion in a fresh process; for the second, open a new terminal in the same checkout. Preserve &lt;code&gt;.ionify&lt;/code&gt; and the output directory, including workspace-root state in a monorepo. Do not change packages or configuration between builds. Check the production output after each invocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make one visible source edit
&lt;/h2&gt;

&lt;p&gt;In a component that your app imports and renders, replace the original button with the second version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&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;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"save-button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&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="c1"&gt;// After&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;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"save-button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save draft&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;These are illustrative JSX fragments; integrate one at a time into your existing component. Run the same build command in another fresh process, keeping local state. Do not add a different mode flag for the edited run.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Serve and inspect the production app
&lt;/h2&gt;

&lt;p&gt;Use your project’s static preview setup against the generated output, not the dev server. The CLI’s default build output is &lt;code&gt;dist&lt;/code&gt;; if your command explicitly selects another directory with &lt;code&gt;--out-dir&lt;/code&gt;, serve that directory consistently. A simple local preview for a root-page app is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 4173 &lt;span class="nt"&gt;--bind&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--directory&lt;/span&gt; dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://127.0.0.1:4173/&lt;/code&gt;. Inspect &lt;code&gt;#save-button&lt;/code&gt; and confirm its visible text is &lt;code&gt;Save draft&lt;/code&gt;. A static Python server does not add SPA routing fallback; use your app’s normal preview server for routes that require it.&lt;/p&gt;

&lt;p&gt;Expected: the new label appears. Observed: fill this in from your browser. No successful run, timing or internal work count is asserted by this walkthrough.&lt;/p&gt;

&lt;p&gt;If the old label appears, preserve the evidence. Check which directory and origin are served and whether the browser reused an earlier response. A prior label experiment saw same-origin 304 reuse display the old label while a fresh origin serving the retained-state output displayed the new one. A new preview origin can help distinguish serving/browser-cache behavior; it is not evidence of universal cache correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the record small and honest
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Installed version / checkout / command:
Same checkout and lockfile: yes/no
Engine state and output retained: yes/no
New process for each invocation: yes/no
Configuration unchanged: yes/no
Expected label / observed label:
Served directory / URL / browser-cache observations:
Elapsed observations, if actually collected:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timing alone cannot reveal which internal work was skipped. Use only diagnostics the installed release supports. For a fresh-state comparison, keep this fixture intact and use a separate disposable copy. Local persistence and cross-machine reuse need different evidence.&lt;/p&gt;

&lt;p&gt;Which part of returning to your project still makes you wait? Share the boundary you tested, even if the result was unfavorable.&lt;/p&gt;

&lt;p&gt;Cover description: “The second build”: a terminal window floats above retained ivory and amber work tiles, with a faded window behind it. Conceptual illustration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Conceptual illustration created with AI assistance for Ionify.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A CSS mutation test for your Ionify pilot</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/a-css-mutation-test-for-your-ionify-pilot-51k</link>
      <guid>https://dev.to/khaledmsalem/a-css-mutation-test-for-your-ionify-pilot-51k</guid>
      <description>&lt;p&gt;A shorter rebuild is useful when the page shows your latest change. This Ionify lab checks one visible result—a border changing from 2px to 6px—while keeping local engine state. Use a disposable, supported app and retain your normal styling packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add an unmistakable rule
&lt;/h2&gt;

&lt;p&gt;In a stylesheet already imported by the app:&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="nc"&gt;.build-lab-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&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;#b77928&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;Apply the class to an element you can easily find. Build with &lt;code&gt;pnpm exec ionify build&lt;/code&gt; and inspect the production output through your normal static preview server.&lt;/p&gt;

&lt;p&gt;Now change only the border width to &lt;code&gt;6px&lt;/code&gt;. Build again without deleting the local engine state. Check the element's computed border width in the browser. Record the result, not just the time.&lt;/p&gt;

&lt;p&gt;Which styling path did you try, and did the production border reach 6px?&lt;/p&gt;

&lt;h2&gt;
  
  
  Exercise the real styling path
&lt;/h2&gt;

&lt;p&gt;If your app uses CSS Modules, repeat with a property in an imported module. If it uses a preprocessor or generated utility CSS, make a change that that pipeline must observe. Do not assume the plain-CSS result covers it.&lt;/p&gt;

&lt;p&gt;Ionify's public setup describes CSS/CSS Modules support. Sass and Less still require their compiler packages. Tailwind and PostCSS need their normal packages and configuration. Follow the &lt;a href="https://ionify.cloud/configs" rel="noopener noreferrer"&gt;current configuration reference&lt;/a&gt; for the version under test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: record enough to debug
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project / report key:
Stylesheet mechanism:
Exact build command:
Starting state / state retained:
Changed source and value:
Expected computed style:
Actual production computed style:
Engine and styling package versions:
Config files involved:
Was a service worker or browser cache active?
Evidence: redacted screenshot or public reproduction URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the style is stale, first confirm the browser is serving the output you just built. Preserve the failure before clearing caches so the reproduction still explains what happened.&lt;/p&gt;

&lt;p&gt;The purpose is a reliable edit-to-check loop. No style change needs to become a migration argument; it just needs to reach the app correctly.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ionify v0.1.38 Is Live: From Native Packages to a Verified Developer Experience</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Wed, 14 Oct 2026 06:39:05 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/ionify-v0138-is-live-from-native-packages-to-a-verified-developer-experience-l0b</link>
      <guid>https://dev.to/khaledmsalem/ionify-v0138-is-live-from-native-packages-to-a-verified-developer-experience-l0b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Ionify v0.1.38 is now published on npm.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This release represents an important milestone for Ionify, a Rust-powered frontend build engine exploring a different approach to build infrastructure: persistent builds rather than repeatedly reconstructing the same knowledge.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What shipped?
&lt;/h2&gt;

&lt;p&gt;Version 0.1.38 is published as nine npm packages: the main &lt;code&gt;@ionify/ionify&lt;/code&gt; package and eight platform-specific native packages covering supported macOS, Windows, and Linux architecture targets.&lt;/p&gt;

&lt;p&gt;The release preserves Ionify's hybrid packaging boundary while distributing native binaries through npm.&lt;/p&gt;

&lt;p&gt;All nine packages are now available at version &lt;code&gt;0.1.38&lt;/code&gt;, with their &lt;code&gt;latest&lt;/code&gt; dist-tags updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification beyond publishing
&lt;/h2&gt;

&lt;p&gt;Publishing packages is only part of a release. The more important question is whether developers can install and actually use them.&lt;/p&gt;

&lt;p&gt;For v0.1.38, I verified the experience against the real npm registry on macOS ARM64 with Node.js 22.20.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fresh installation of &lt;code&gt;@ionify/ionify@0.1.38&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Native module loading and CLI execution&lt;/li&gt;
&lt;li&gt;Actual &lt;code&gt;npm create ionify@latest&lt;/code&gt; scaffolding&lt;/li&gt;
&lt;li&gt;Dependency installation and successful build&lt;/li&gt;
&lt;li&gt;Development server startup&lt;/li&gt;
&lt;li&gt;Chrome navigation, direct route loading and page reload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser run completed without observed runtime errors. React Router future-flag warnings were recorded, and verification on other operating systems remains a separate concern.&lt;/p&gt;

&lt;p&gt;The existing &lt;code&gt;create-ionify@0.1.1&lt;/code&gt; package continues to work without republishing, resolving the latest Ionify engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ionify exists
&lt;/h2&gt;

&lt;p&gt;Traditional frontend build workflows often repeat work across development, production builds and CI.&lt;/p&gt;

&lt;p&gt;Ionify is built around a different architectural direction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One Graph. One CAS. One Authority.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal is to make verified build facts reusable across lifecycle boundaries, while preserving deterministic execution and explicit ownership of build artifacts.&lt;/p&gt;

&lt;p&gt;This release doesn't mean that every part of that vision is finished. It means the current published engine has passed a real consumer installation and startup path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;The next engineering focus is bringing development, production artifact prewarming and production builds closer to a shared persistent authority.&lt;/p&gt;

&lt;p&gt;That means improving reuse, reducing redundant transforms and validating correctness across lifecycle transitions.&lt;/p&gt;

&lt;p&gt;The long-term goal isn't simply to make another bundler.&lt;/p&gt;

&lt;p&gt;It's to make frontend build infrastructure remember what it already knows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try Ionify:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create ionify@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Website: &lt;a href="https://ionify.cloud" rel="noopener noreferrer"&gt;https://ionify.cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/@ionify/ionify" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@ionify/ionify&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  javascript #rust #webdev #buildtools
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>react</category>
    </item>
    <item>
      <title>Compare a leaf edit with a shared-helper edit</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Mon, 12 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/compare-a-leaf-edit-with-a-shared-helper-edit-52la</link>
      <guid>https://dev.to/khaledmsalem/compare-a-leaf-edit-with-a-shared-helper-edit-52la</guid>
      <description>&lt;p&gt;A tiny edit should not leave you guessing about the next build. This Ionify lab compares a footer-label change with a shared-helper change, so you can see what reached the production page and measure the wait from a comparable starting state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small shared path
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/formatLabel.ts&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;formatLabel&lt;/span&gt; &lt;span class="o"&gt;=&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="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="nx"&gt;name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the helper in two components that your app actually imports and 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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatLabel&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;./formatLabel&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;Header&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;formatLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; Build Lab &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;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;Footer&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;footer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;formatLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; Ready &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;lt;/&lt;/span&gt;&lt;span class="nt"&gt;footer&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 example illustrates the relationship. Adapt component placement and imports to your app rather than introducing duplicate exports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case A: local visible text
&lt;/h2&gt;

&lt;p&gt;Establish a build with &lt;code&gt;pnpm exec ionify build&lt;/code&gt;. Change “Ready” to “Ready to test” in the footer. Build again. Check the production footer and record time, starting state and output differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case B: shared behavior
&lt;/h2&gt;

&lt;p&gt;Return to the same baseline in your disposable checkout and establish it again. Change the helper from &lt;code&gt;name.trim()&lt;/code&gt; to &lt;code&gt;name.trim().toUpperCase()&lt;/code&gt;. Build and verify that both header and footer change.&lt;/p&gt;

&lt;p&gt;Do not compare Case B against an unrelated cold baseline. Keep the starting state and measurement boundary explicit. Repeat both experiments rather than reporting a single favorite run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpret the difference
&lt;/h2&gt;

&lt;p&gt;Both cases modify one file. Case B affects multiple consumers. A correct engine may need wider work, even if an unchanged artifact remains reusable elsewhere.&lt;/p&gt;

&lt;p&gt;A faster timer is not proof of a particular invalidation path. Pair it with available diagnostics and output checks. If both consumers do not update, minimize that reproduction and &lt;a href="https://github.com/ionifyjs/ionify/issues" rel="noopener noreferrer"&gt;report it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The developer takeaway is practical: a source file's size and its change's reach are different things. Which case changed both consumers in your test? A one-line answer is welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to share a reproducible report
&lt;/h2&gt;

&lt;p&gt;Include a project/report key, tool version, exact command, starting state, mutation, expected/actual output and a redacted screenshot or public reproduction.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Use an output inventory to investigate a repeat build</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Fri, 09 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/use-an-output-inventory-to-investigate-a-repeat-build-3pd</link>
      <guid>https://dev.to/khaledmsalem/use-an-output-inventory-to-investigate-a-repeat-build-3pd</guid>
      <description>&lt;p&gt;A production build returning exit code zero is necessary, but it does not tell you which artifacts changed. This lab adds a small read-only inventory helper. It does not claim to prove runtime correctness.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;inventory.mjs&lt;/code&gt; outside the output directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readFile&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;node:fs/promises&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;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;join&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;node: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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&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;entries&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;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;withFileTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;a&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="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;walk&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="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFile&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;bytes&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;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&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;join&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;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="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="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helper includes regular files and skips symbolic links. Use the actual output directory if it is not &lt;code&gt;dist&lt;/code&gt;. It fails if the directory cannot be read rather than treating a missing build as an empty success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare two states
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;ionify build
node inventory.mjs dist &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; before.json
&lt;span class="c"&gt;# Change one visible string in an imported component.&lt;/span&gt;
pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;ionify build
node inventory.mjs dist &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; after.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the JSON files outside &lt;code&gt;dist&lt;/code&gt;. Compare filenames, byte counts and hashes with your normal diff tool. Content-hashed names can turn a change into an addition and removal, so do not compare only matching filenames.&lt;/p&gt;

&lt;p&gt;Serve the production output and check the edited label. Repeat without another source change and inspect any unexpected artifact differences. Timestamps, source maps or environment-dependent content can require explanation; a changed hash alone does not identify the cause.&lt;/p&gt;

&lt;p&gt;This is a diagnostic tool, not an Ionify feature or a security audit. It helps turn “the build felt strange” into a precise observation that a maintainer can investigate.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Add an Ionify pilot command while keeping your existing build</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Wed, 07 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/add-an-ionify-pilot-command-while-keeping-your-existing-build-1lk9</link>
      <guid>https://dev.to/khaledmsalem/add-an-ionify-pilot-command-while-keeping-your-existing-build-1lk9</guid>
      <description>&lt;p&gt;This lab adds an experimental path without replacing your current one. Use a branch or separate checkout, not an unreviewed change to production CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the prerequisites
&lt;/h2&gt;

&lt;p&gt;Read the &lt;a href="https://ionify.cloud/getting-started" rel="noopener noreferrer"&gt;current setup guide&lt;/a&gt; and support boundaries. The public 0.1.37 package declares Node 22.x or 24.x. Keep optional dependencies enabled because they carry the native engine. Check the installed package if you use a newer release.&lt;/p&gt;

&lt;p&gt;Install into the pilot checkout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @ionify/ionify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record the resolved version from your lockfile. Avoid describing an experiment only as “latest.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Point at your actual entry
&lt;/h2&gt;

&lt;p&gt;For an app whose entry is &lt;code&gt;src/main.tsx&lt;/code&gt;, create &lt;code&gt;ionify.config.ts&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;@ionify/ionify&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;entry&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/main.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config does not create an app or replace required HTML/framework configuration. Adapt it to the project rather than copying a path that does not exist.&lt;/p&gt;

&lt;p&gt;Add these scripts alongside your existing scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev:ionify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ionify dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build:ionify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ionify build"&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;p&gt;The snippet is the content to add inside &lt;code&gt;package.json&lt;/code&gt;'s existing &lt;code&gt;scripts&lt;/code&gt; object, not a replacement package file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the app before the timer
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;pnpm run dev:ionify&lt;/code&gt;. Check a route, a static asset, styling and a dynamic import used by the app. Then run &lt;code&gt;pnpm run build:ionify&lt;/code&gt; and check the production output through your normal preview server.&lt;/p&gt;

&lt;p&gt;Record the first build, an unchanged build and a build after a visible component edit separately. Do not run two engines concurrently into the same output directory. Preserve each output for comparison or use isolated checkouts.&lt;/p&gt;

&lt;p&gt;Keep the existing deployment command until correctness, integration support and the repeated workflow have been evaluated. If the pilot does not help, you have learned that cheaply. If it fails, the isolated checkout is already the beginning of a useful reproduction.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Measure three build states without mixing them together</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/measure-three-build-states-without-mixing-them-together-599d</link>
      <guid>https://dev.to/khaledmsalem/measure-three-build-states-without-mixing-them-together-599d</guid>
      <description>&lt;p&gt;A useful build test tells you which wait improved. This lab gives you three observations you can compare later: first run, unchanged run and one visible edit.&lt;/p&gt;

&lt;p&gt;Use a disposable checkout of a supported app with Ionify already configured. Keep your normal build command. Check the &lt;a href="https://ionify.cloud/getting-started" rel="noopener noreferrer"&gt;setup requirements&lt;/a&gt;, including the package's Node version and native optional dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Name the starting state
&lt;/h2&gt;

&lt;p&gt;Record the installed Ionify version, Node version, operating system, package lockfile and configuration. For the first run, say whether local engine state or output already exists. Do not call a run cold merely because you opened a new terminal.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;ionify build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use your shell's elapsed-time facility or an external timer around the complete invocation. State whether the number includes process startup. Do not mix a tool's internal phase timer with an external wall-clock time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Repeat without a change
&lt;/h2&gt;

&lt;p&gt;Keep the local &lt;code&gt;.ionify&lt;/code&gt; state and output directory. Run the same command again. Record this as an unchanged build, separately from the first run.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make a visible edit
&lt;/h2&gt;

&lt;p&gt;In an imported component, change a literal label such as “Save” to “Save draft.” Run the command again, serve the production output using your normal static preview setup, and verify that the new label appears. Then make one more unchanged run.&lt;/p&gt;

&lt;p&gt;Copy this result template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool and version:
Node / OS / hardware:
Command and timer boundary:
Starting engine/output state:
First run:
Unchanged run:
Edited run:
Next unchanged run:
Correct output checked:
Raw observations / reproduction:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat the sequence, preserve every trial and note background activity. Do not silently discard a slow run.&lt;/p&gt;

&lt;p&gt;Ionify aims to reuse valid local work. This test does not establish remote-cache behavior, HMR latency or compatibility with every integration. A single edited file can still require wider work. The result that matters is a shorter wait for the correct app.&lt;/p&gt;

&lt;p&gt;If something fails, share a minimal reproduction through &lt;a href="https://github.com/ionifyjs/ionify/issues" rel="noopener noreferrer"&gt;Ionify's issue tracker&lt;/a&gt;, without secrets or private source.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ionify 0.1.37 Is Here: 30ms No-Change Builds and ~120ms One-File Rebuilds</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Wed, 23 Sep 2026 08:48:08 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/ionify-0137-is-here-30ms-no-change-builds-and-120ms-one-file-rebuilds-5hb1</link>
      <guid>https://dev.to/khaledmsalem/ionify-0137-is-here-30ms-no-change-builds-and-120ms-one-file-rebuilds-5hb1</guid>
      <description>&lt;p&gt;Ionify 1.0.37 is here.&lt;/p&gt;

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

&lt;p&gt;⠀&lt;br&gt;
Here’s the part I care about most.&lt;br&gt;
⠀&lt;br&gt;
Test project:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
~15,000 React components
⠀
~25,000 dependencies
⠀
Production build
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
Vite 8:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
Initial build        ~2.7s
⠀
No-change build      ~2.7s
⠀
One-file change      ~2.7s
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
Ionify:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
Initial build        ~5s
⠀
No-change build      ~30ms
⠀
One-file change      ~120ms
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
Yes, Ionify currently loses the first build.&lt;br&gt;
⠀&lt;br&gt;
That is something I’m actively working on.&lt;br&gt;
⠀&lt;br&gt;
But the interesting part starts after the first build.&lt;br&gt;
⠀&lt;br&gt;
If nothing changed, Ionify tries not to rebuild anything.&lt;br&gt;
⠀&lt;br&gt;
If one source changed, the goal is to rebuild the affected work rather than traverse and reproduce the entire project again.&lt;br&gt;
⠀&lt;br&gt;
Conceptually:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
traditional repeated build
⠀
project
⠀
  ↓
⠀
scan
⠀
  ↓
⠀
resolve
⠀
  ↓
⠀
transform
⠀
  ↓
⠀
bundle
⠀
  ↓
⠀
emit
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
Ionify increasingly behaves more like:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
known project state
⠀
       ↓
⠀
what changed?
⠀
       ↓
⠀
affected work
⠀
       ↓
⠀
reuse everything else
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
That sounds obvious.&lt;br&gt;
⠀&lt;br&gt;
But at scale, the difference becomes significant.&lt;br&gt;
⠀&lt;br&gt;
A build system shouldn’t only become faster because parsing, transforms or bundling became faster.&lt;br&gt;
⠀&lt;br&gt;
It should become faster because most of the previous work no longer needs to happen.&lt;br&gt;
⠀&lt;/p&gt;
&lt;h2&gt;
  
  
  Fewer plugins by default
&lt;/h2&gt;

&lt;p&gt;⠀&lt;br&gt;
There is another part of Ionify that I’ve learned matters a lot when showing it to frontend developers:&lt;br&gt;
⠀&lt;br&gt;
&lt;strong&gt;plugin fatigue is real.&lt;/strong&gt;&lt;br&gt;
⠀&lt;br&gt;
Plugins are useful. Ionify supports them, and the README now documents the supported plugin surface.&lt;br&gt;
⠀&lt;br&gt;
But the engine shouldn’t require a plugin for every fundamental part of the frontend workflow.&lt;br&gt;
⠀&lt;br&gt;
My direction with Ionify is:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
strong core
⠀
+
⠀
plugins when you actually need extension
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
rather than:&lt;br&gt;
⠀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠀
small core
⠀
+
⠀
plugins required to assemble the normal workflow
⠀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⠀&lt;br&gt;
So the practical pitch for Ionify is becoming very simple:&lt;br&gt;
⠀&lt;br&gt;
&lt;strong&gt;Fast builds. Fewer plugins. Less repeated work.&lt;/strong&gt;&lt;br&gt;
⠀&lt;br&gt;
The architecture underneath that is more complicated, of course.&lt;br&gt;
⠀&lt;br&gt;
Ionify keeps persistent knowledge about previous work, tracks ownership of verified results, and tries to invalidate only the work affected by a change.&lt;br&gt;
⠀&lt;br&gt;
But you shouldn’t need to understand the architecture before the product becomes useful.&lt;br&gt;
⠀&lt;br&gt;
The result should explain itself.&lt;br&gt;
⠀&lt;br&gt;
Change one file.&lt;br&gt;
⠀&lt;br&gt;
Build again.&lt;br&gt;
⠀&lt;br&gt;
And watch how little work actually needs to happen.&lt;br&gt;
⠀&lt;br&gt;
Ionify is open source and still early.&lt;br&gt;
⠀&lt;br&gt;
0.1.37 is another step toward making frontend builds incremental by default rather than repeatedly rebuilding what the system already knows.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>react</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Vite Builds My Project in 2.7s. The Next Ionify Build Takes 50ms. Here's Why.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:33:17 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/vite-builds-my-project-in-27s-the-next-ionify-build-takes-50ms-heres-why-kic</link>
      <guid>https://dev.to/khaledmsalem/vite-builds-my-project-in-27s-the-next-ionify-build-takes-50ms-heres-why-kic</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm2njdy87xzk6sv9nbni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm2njdy87xzk6sv9nbni.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vite is fast.&lt;/p&gt;

&lt;p&gt;I'm not trying to argue otherwise.&lt;/p&gt;

&lt;p&gt;Its development experience and HMR are a big part of why it became the default choice for a huge part of the frontend ecosystem.&lt;/p&gt;

&lt;p&gt;But I wanted to test something else:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when we repeatedly run production builds on a large project?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I benchmarked Vite against Ionify using a real React project with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15,000 React components&lt;/li&gt;
&lt;li&gt;25,000 dependencies&lt;/li&gt;
&lt;li&gt;Production builds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the results.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Build&lt;/th&gt;
&lt;th&gt;Vite&lt;/th&gt;
&lt;th&gt;Ionify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial build&lt;/td&gt;
&lt;td&gt;2.7s&lt;/td&gt;
&lt;td&gt;6s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No changes&lt;/td&gt;
&lt;td&gt;2.7s&lt;/td&gt;
&lt;td&gt;50ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One file changed&lt;/td&gt;
&lt;td&gt;2.7s&lt;/td&gt;
&lt;td&gt;120ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The obvious result is that Vite wins the first build.&lt;/p&gt;

&lt;p&gt;2.7 seconds versus 6 seconds.&lt;/p&gt;

&lt;p&gt;No argument there.&lt;/p&gt;

&lt;p&gt;But look at what happens afterward.&lt;/p&gt;

&lt;p&gt;Vite's build time stays roughly constant.&lt;/p&gt;

&lt;p&gt;Ionify drops from seconds to milliseconds.&lt;/p&gt;

&lt;p&gt;That's the behavior I care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the second build change so much?
&lt;/h2&gt;

&lt;p&gt;Ionify doesn't treat every build as a completely new execution.&lt;/p&gt;

&lt;p&gt;The first build establishes verified knowledge about the project.&lt;/p&gt;

&lt;p&gt;After that, the engine attempts to reuse that knowledge rather than recompute it.&lt;/p&gt;

&lt;p&gt;So a no-change build shouldn't mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scan everything
resolve everything
transform everything
bundle everything
discover that nothing changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should be much closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;load known project state
verify identities
observe no meaningful change
reuse published results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if one file changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;identify affected work
invalidate only what depends on it
reuse everything else
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Authority
&lt;/h2&gt;

&lt;p&gt;Internally, I describe this architecture as &lt;strong&gt;Build Authority&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The basic rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every category of verified work should have one authoritative owner.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, imagine multiple stages in a frontend toolchain independently deciding how a dependency should be resolved.&lt;/p&gt;

&lt;p&gt;Even if every stage is fast, you're still repeating knowledge.&lt;/p&gt;

&lt;p&gt;Authority says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependency resolution
        ↓
single authority
        ↓
published verified result
        ↓
dev / build / CI consume it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dev resolves dependencies
build resolves dependencies
optimizer resolves dependencies
CI resolves dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't just faster code.&lt;/p&gt;

&lt;p&gt;The goal is eliminating repeated reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is different from "just cache it"
&lt;/h2&gt;

&lt;p&gt;A cache tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I have an artifact for key X.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authority also tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This system owns the truth for X,
this result was produced under these conditions,
and downstream consumers should not independently recreate it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you a stronger invariant.&lt;/p&gt;

&lt;p&gt;Instead of multiple systems producing equivalent answers and hoping they agree, one layer publishes the answer and everyone else consumes it.&lt;/p&gt;

&lt;p&gt;That is particularly useful when dealing with things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependency resolution&lt;/li&gt;
&lt;li&gt;transformed modules&lt;/li&gt;
&lt;li&gt;dependency publication&lt;/li&gt;
&lt;li&gt;dependency export surfaces&lt;/li&gt;
&lt;li&gt;CSS artifacts&lt;/li&gt;
&lt;li&gt;build graphs&lt;/li&gt;
&lt;li&gt;production plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture starts becoming less about a pipeline of repeated computations and more about a graph of published knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6-second problem
&lt;/h2&gt;

&lt;p&gt;There is one number in the table I definitely don't want to hand-wave away:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ionify's first build takes around 6 seconds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's more than twice the Vite result in this benchmark.&lt;/p&gt;

&lt;p&gt;It's currently the weak point.&lt;/p&gt;

&lt;p&gt;Some of that cost comes from establishing the state that later builds reuse.&lt;/p&gt;

&lt;p&gt;But architectural justification doesn't make six seconds good.&lt;/p&gt;

&lt;p&gt;So first-build performance is the next area I'm focusing on.&lt;/p&gt;

&lt;p&gt;The ideal outcome isn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;slow first build
fast later builds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fast first build
almost nothing to do afterward
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The bigger question
&lt;/h2&gt;

&lt;p&gt;The interesting question for me isn't really:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can Ionify beat Vite in a benchmark?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much frontend build work exists only because our tools keep forgetting what they already know?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compilers already understand incremental computation.&lt;/p&gt;

&lt;p&gt;Build systems already understand dependency graphs.&lt;/p&gt;

&lt;p&gt;Content-addressable storage already understands immutable identity.&lt;/p&gt;

&lt;p&gt;The experiment behind Ionify is to combine those ideas around one rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;verified knowledge should persist until something actually invalidates it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what produces the 50ms no-change build.&lt;/p&gt;

&lt;p&gt;Not because the engine executes the same build dramatically faster.&lt;/p&gt;

&lt;p&gt;Because most of the build no longer needs to happen.&lt;/p&gt;

&lt;p&gt;The full Build Authority model is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ionify.cloud/build-authority" rel="noopener noreferrer"&gt;https://ionify.cloud/build-authority&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ionify is open source and still early.&lt;/p&gt;

&lt;p&gt;If you're interested in build systems, I'd actually prefer you try to break the model rather than just trust the benchmark.&lt;/p&gt;

&lt;p&gt;Run it against a real project.&lt;/p&gt;

&lt;p&gt;Change weird things.&lt;/p&gt;

&lt;p&gt;Invalidate dependencies.&lt;/p&gt;

&lt;p&gt;Push it into cases where incremental systems normally fall apart.&lt;/p&gt;

&lt;p&gt;Then tell me where the authority model stops holding.&lt;/p&gt;

&lt;p&gt;Starting Ionify on GitHub to help keep development at:&lt;a href="https://github.com/ionifyjs/ionify" rel="noopener noreferrer"&gt;https://github.com/ionifyjs/ionify&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
