<?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>Your build tool has amnesia. I built one that remembers.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Fri, 26 Jun 2026 19:07:07 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/your-build-tool-has-amnesia-i-built-one-that-remembers-1j9p</link>
      <guid>https://dev.to/khaledmsalem/your-build-tool-has-amnesia-i-built-one-that-remembers-1j9p</guid>
      <description>&lt;p&gt;This is not a "Vite is dead" post. I love Vite. It's the reason dev servers stopped being painful. What I'm about to describe doesn't make Vite worse — it points at something &lt;em&gt;every&lt;/em&gt; fast build tool still does, including the Rust ones.&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%2Fr0eb8y1d4k3wojki7sif.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%2Fr0eb8y1d4k3wojki7sif.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here it is: &lt;strong&gt;your build tool has amnesia.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every layer of computing eventually learned the same lesson — stop redoing work you've already done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPUs → caches&lt;/li&gt;
&lt;li&gt;Databases → indexes &amp;amp; materialized views&lt;/li&gt;
&lt;li&gt;Networks → the CDN&lt;/li&gt;
&lt;li&gt;Compilers → incremental compilation&lt;/li&gt;
&lt;li&gt;The frontend → memoization &amp;amp; query caches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One layer never learned it: the build.&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;build&lt;/code&gt; rediscovers the same dependency behavior, re-transforms the same modules, and rebuilds the same artifacts — from near zero. Vite, Rollup, esbuild, even Turbopack and Rspack made each &lt;em&gt;run&lt;/em&gt; faster. But each run still starts from nothing. They optimized the &lt;strong&gt;speed of forgetting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ionify.cloud" rel="noopener noreferrer"&gt;Ionify&lt;/a&gt; is what happens when you refuse to accept that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not a faster bundler — a different category
&lt;/h2&gt;

&lt;p&gt;Ionify is a Rust-powered build engine that replaces disposable execution with &lt;strong&gt;persistent build intelligence&lt;/strong&gt;. Instead of rediscovering dependency behavior and rebuilding the same artifacts every time, it persists the project's knowledge — the dependency graph, transformed artifacts, dependency contracts — and reuses valid work across dev, CI, and production.&lt;/p&gt;

&lt;p&gt;The shift is from build &lt;em&gt;execution&lt;/em&gt; to build &lt;em&gt;intelligence&lt;/em&gt;. Execution throws its knowledge away after every run. Intelligence keeps it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number
&lt;/h2&gt;

&lt;p&gt;On a real production app — &lt;strong&gt;11,000+ modules, 25,000+ dependencies&lt;/strong&gt; — Ionify produces a warm build in &lt;strong&gt;196ms&lt;/strong&gt;, deterministically. The number isn't the headline; it's the proof the model works at scale.&lt;/p&gt;

&lt;p&gt;What's actually persisted and reused:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Persistent dependency graph&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content-Addressable Storage (CAS)&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native dependency resolver&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Publication Layer (DPL)&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unified dev + build pipeline&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Federation foundation&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud CAS&lt;/td&gt;
&lt;td&gt;In progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI optimization layer&lt;/td&gt;
&lt;td&gt;Planned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I'd rather tell you what's stable and what isn't than sell you a roadmap as a fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works, in four ideas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One Graph.&lt;/strong&gt; A persistent dependency graph that survives restarts, branch switches, dev/build transitions, and workspace boundaries. The graph is infrastructure, not temporary state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One CAS.&lt;/strong&gt; Every transformed artifact is stored by identity. If the inputs haven't changed, Ionify reuses the existing artifact instead of recomputing it. Artifacts are immutable; valid work is never redone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One Dependency Authority.&lt;/strong&gt; Dependencies are analyzed once and published as reusable contracts, consumed by the dev server, the production bundler, federation, and (soon) the cloud. No drift. No multiple realities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One Pipeline.&lt;/strong&gt; Most tooling quietly maintains a dev reality, a build reality, a CI reality, and a production reality — and they disagree. Ionify runs one deterministic pipeline so development and production &lt;em&gt;can't&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Getting started looks like any other tool:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/src/main.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm ionify dev
pnpm ionify build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The bug that made me build the DPL
&lt;/h2&gt;

&lt;p&gt;The "One Dependency Authority" idea didn't come from a whiteboard. It came from a production bug.&lt;/p&gt;

&lt;p&gt;In a real build, shared chunks were importing symbols from entry chunks that those entry chunks &lt;strong&gt;never actually exported&lt;/strong&gt;. Invalid ESM relationships that passed tests and then waited to break at runtime. The root cause wasn't a single bad line — it was that &lt;em&gt;no part of the pipeline owned the truth about what a module exports.&lt;/em&gt; Each stage re-derived the export surface, and they quietly disagreed.&lt;/p&gt;

&lt;p&gt;The fix became the &lt;strong&gt;Dependency Publication Layer&lt;/strong&gt;: publish a dependency's export surface exactly once as a content-hashed &lt;strong&gt;export ABI manifest&lt;/strong&gt;, make that manifest the single export authority, and put a &lt;strong&gt;fail-closed loader&lt;/strong&gt; in front of it that refuses to hydrate raw &lt;code&gt;node_modules&lt;/code&gt; without a verified contract. Published contracts also carry an export-surface hash, singleton ownership, and a deterministic artifact identity.&lt;/p&gt;

&lt;p&gt;The principle: &lt;strong&gt;one dependency, one contract, one authority.&lt;/strong&gt; That's what kills duplicate Reacts, version drift, and phantom exports — not at lint time, but structurally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish before build
&lt;/h2&gt;

&lt;p&gt;Here's the part that surprises people. The work to produce production was already &lt;em&gt;latent&lt;/em&gt; in your dev session — and every tool throws it away. Ionify doesn't.&lt;/p&gt;

&lt;p&gt;While you develop, Ionify can publish production-keyed artifacts ahead of time, so your first production build after a dev session approaches warm-build speed:&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;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;productionArtifactPublishing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&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;The guardrails are the whole point — this is not a cache trick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ionify build&lt;/code&gt; stays the &lt;strong&gt;verifier&lt;/strong&gt;. Published artifacts are optional accelerators it must re-validate.&lt;/li&gt;
&lt;li&gt;Publication writes only under &lt;code&gt;.ionify&lt;/code&gt; — never &lt;code&gt;dist/&lt;/code&gt;, never deploy output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production namespace only.&lt;/strong&gt; Dev artifacts can't masquerade as production ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail-closed.&lt;/strong&gt; If anything is missing, stale, or incompatible, build repairs via the normal production path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speed without giving up correctness — or it doesn't ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a different axis
&lt;/h2&gt;

&lt;p&gt;Vite won on dev speed. Turbopack and Rspack are racing on raw speed too. But that's all the &lt;em&gt;same&lt;/em&gt; axis — faster execution. Ionify competes on a different one: &lt;strong&gt;accumulated intelligence&lt;/strong&gt;. When you change the axis, the question changes from "is this faster?" to "why did we ever accept that the build forgets everything, every single time?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://ionify.cloud" rel="noopener noreferrer"&gt;https://ionify.cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub (a star helps a lot): &lt;a href="https://github.com/ionifyjs/ionify" rel="noopener noreferrer"&gt;https://github.com/ionifyjs/ionify&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is post 1 in a series. Next up: how Ionify &lt;strong&gt;publishes dependency contracts&lt;/strong&gt; — the DPL in depth, and why "one dependency, one authority" is the end of duplicate Reacts and phantom exports.&lt;/p&gt;

&lt;p&gt;What would your CI look like if it never started over? I'd genuinely like to hear where your builds waste the most time — drop it in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>rust</category>
      <category>performance</category>
    </item>
    <item>
      <title>The question that actually decides your state stack (it's not 'Redux or React Query')</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sun, 21 Jun 2026 09:39:24 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/the-question-that-actually-decides-your-state-stack-its-not-redux-or-react-query-49gp</link>
      <guid>https://dev.to/khaledmsalem/the-question-that-actually-decides-your-state-stack-its-not-redux-or-react-query-49gp</guid>
      <description>&lt;p&gt;This is not a "Redux is dead" post. Redux is excellent at what it's for. So is React Query. So is Zustand.&lt;/p&gt;

&lt;p&gt;The problem was never the tools. It was that I spent years comparing tools that answer &lt;strong&gt;completely different questions&lt;/strong&gt; — and never noticed.&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%2Futicz9708fnkx815g84g.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%2Futicz9708fnkx815g84g.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Redux or React Query?" "Zustand or RTK?" These read like real choices. They're not. It's like asking &lt;em&gt;"hammer or screwdriver?"&lt;/em&gt; before you know what you're building.&lt;/p&gt;

&lt;p&gt;The decision that actually matters comes from one question almost nobody asks correctly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who owns the source of truth for each piece of state?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  There isn't one kind of state. There are two.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Owned truth.&lt;/strong&gt; The client is the source. Is the sidebar open, what's the theme, which step of the wizard are we on, what's selected right now. Nobody tells this state to go re-verify itself. It lives and dies by your decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Borrowed truth.&lt;/strong&gt; The source lives somewhere else — the server. Whatever you hold on the client is a &lt;em&gt;mirror&lt;/em&gt; of a truth that changes behind your back. Its entire job is staying close to reality: staleness, invalidation, deduping, refetch-on-focus, optimistic updates.&lt;/p&gt;

&lt;p&gt;These are not two flavors of the same thing. The first is a &lt;em&gt;storage&lt;/em&gt; problem. The second is a &lt;em&gt;distributed-systems synchronization&lt;/em&gt; problem. They do not share a mental model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original sin
&lt;/h2&gt;

&lt;p&gt;A decade of frontend pain came from one move: using &lt;strong&gt;one tool for both&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's borrowed truth managed as if you owned it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The anti-pattern: server state hand-managed in a reducer&lt;/span&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;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;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;usersReducer&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="s1"&gt;FETCH_USERS_START&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;loading&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FETCH_USERS_SUCCESS&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="na"&gt;users&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;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;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="s1"&gt;FETCH_USERS_ERROR&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;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;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;span class="c1"&gt;// Now hand-write the rest yourself:&lt;/span&gt;
&lt;span class="c1"&gt;// caching, dedup, staleness windows, refetch-on-focus, retries, pagination...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every line of that is you re-implementing React Query — badly, without the guarantees. The &lt;code&gt;redux + sagas&lt;/code&gt; swamp people remember wasn't Redux being bad. It was &lt;em&gt;borrowed truth shoved into a tool built for owned truth&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Split the two, and the swamp drains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Borrowed truth → React Query owns the hard part&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fetchUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// caching, dedup, staleness, refetch, retries — handled&lt;/span&gt;

&lt;span class="c1"&gt;// Owned truth → Zustand owns the trivial part&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useUI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;sidebarOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;toggleSidebar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;sidebarOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sidebarOpen&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The famous combos are answers, not preferences
&lt;/h2&gt;

&lt;p&gt;Once you split state by who owns the truth, the stacks stop being taste and start being &lt;em&gt;derivations&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  react-query + zustand — the separation bet
&lt;/h3&gt;

&lt;p&gt;Most of your state turns out to be borrowed truth that was &lt;em&gt;masquerading&lt;/em&gt; as client state. Once React Query absorbs it, what's left of your owned truth is small — so Zustand's near-zero-ceremony store is more than enough. React Query takes the hard distributed-systems problem; Zustand takes the trivial store. The modern default.&lt;/p&gt;

&lt;p&gt;The one discipline it demands: &lt;strong&gt;never&lt;/strong&gt; dump server data into Zustand. The boundary is the whole point.&lt;/p&gt;

&lt;h3&gt;
  
  
  rq + redux + sagas — the orchestration bet
&lt;/h3&gt;

&lt;p&gt;This is the one people misread. &lt;strong&gt;Sagas are not a data-fetching tool. They're a concurrency-and-time model&lt;/strong&gt; — CSP/coroutines for the frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sagas model time &amp;amp; concurrency, not data&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;searchSaga&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;takeLatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SEARCH_INPUT&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="o"&gt;*&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;yield&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                      &lt;span class="c1"&gt;// debounce&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&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;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// auto-cancels the previous run&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SEARCH_RESULTS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&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;You reach for this when client complexity isn't &lt;em&gt;holding values&lt;/em&gt; — it's &lt;em&gt;orchestrating processes over time&lt;/em&gt;: cancellable, concurrent, raced flows, websocket streams feeding a state machine, multi-step wizards with rollback. And it's &lt;code&gt;redux&lt;/code&gt; specifically — not Zustand — because sagas need a central &lt;strong&gt;action stream&lt;/strong&gt; to subscribe to. The Redux action log &lt;em&gt;is&lt;/em&gt; the event bus the saga listens on.&lt;/p&gt;

&lt;p&gt;Here's the catch: React Query already ate the &lt;em&gt;data-fetching&lt;/em&gt; orchestration. What's left for sagas is pure client-side concurrency. &lt;strong&gt;If, after adopting React Query, your sagas have almost nothing to do — that's the signal you never needed them.&lt;/strong&gt; Justified for trading terminals, collaborative editors, real-time control UIs. Not your average CRUD app. (And in 2026, &lt;a href="https://stately.ai/" rel="noopener noreferrer"&gt;XState&lt;/a&gt; is often a cleaner answer than sagas for this axis — an explicit state machine beats an implicit one.)&lt;/p&gt;

&lt;h3&gt;
  
  
  RTK only — the unification bet
&lt;/h3&gt;

&lt;p&gt;The opposite philosophy: don't specialize, &lt;strong&gt;unify&lt;/strong&gt;. RTK Query handles borrowed truth, slices handle owned truth, all in one store, one tree, one set of devtools.&lt;/p&gt;

&lt;p&gt;Why that's a real architectural argument, not laziness: you get &lt;strong&gt;one serializable snapshot of the entire app&lt;/strong&gt; — server cache + client state in a single object. That makes &lt;code&gt;logout → wipe everything&lt;/code&gt; a one-liner, time-travel debugging span the &lt;em&gt;whole&lt;/em&gt; app including the cache, SSR hydration of all of it, and offline persistence trivial. &lt;code&gt;react-query + zustand&lt;/code&gt; can't hand you one clean snapshot of the universe, because they're two separate universes.&lt;/p&gt;

&lt;p&gt;The bet: unification is worth more than React Query's edge-case ergonomics. Enterprise's friend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Borrowed-truth ratio&lt;/th&gt;
&lt;th&gt;Client complexity&lt;/th&gt;
&lt;th&gt;One app snapshot?&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;react-query + zustand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;holding values&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;lowest&lt;/td&gt;
&lt;td&gt;small, senior teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rq + redux + sagas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;orchestrating time&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;highest&lt;/td&gt;
&lt;td&gt;concurrency-heavy domains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RTK only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;medium–high&lt;/td&gt;
&lt;td&gt;values + unification&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;large enterprise teams&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The function, in plain terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mostly borrowed truth + client just holds values → &lt;strong&gt;react-query + zustand&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Client orchestrates processes over time → &lt;strong&gt;rq + redux + sagas&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Need one snapshot of the whole universe → &lt;strong&gt;RTK only&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(Diagram: the staircase decision tree goes here — each question peels off one answer.)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The uncomfortable part for 2026
&lt;/h2&gt;

&lt;p&gt;React Server Components and server actions are quietly pushing a large chunk of this state &lt;em&gt;back to the server&lt;/em&gt;. The app whose state was half client-state is watching RSC eat that half. So the deepest question now sits one level above everything above:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this state need to live on the client at all?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Don't pick a state tool. Figure out who owns the truth and where it lives — the tool derives itself from the answer.&lt;/p&gt;

&lt;p&gt;What's your current stack, and which of these three is it &lt;em&gt;really&lt;/em&gt;? Drop it in the comments 👇&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Cache and Memory Are Not the Same Thing. Frontend Engineers Keep Treating Them As If They Are.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sat, 20 Jun 2026 15:17:04 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/cache-and-memory-are-not-the-same-thing-frontend-engineers-keep-treating-them-as-if-they-are-kgc</link>
      <guid>https://dev.to/khaledmsalem/cache-and-memory-are-not-the-same-thing-frontend-engineers-keep-treating-them-as-if-they-are-kgc</guid>
      <description>&lt;p&gt;We use these words interchangeably all the time.&lt;/p&gt;

&lt;p&gt;"The browser caches it." "React Query caches the response." "We memoize that calculation." "Service worker cache." "In-memory cache."&lt;/p&gt;

&lt;p&gt;Cache. Memory. Memoization. Same bucket, different labels.&lt;/p&gt;

&lt;p&gt;They're not the same thing. And the difference isn't pedantic — it's architectural, and it changes how you should design systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start with what they actually are
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; is a place where a system stores something it owns. The value exists because the system put it there, and the system is the source of truth for what that value is. If memory says X, X is true, by definition, because nothing external defines X.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache&lt;/strong&gt; is a place where a system stores a &lt;em&gt;copy&lt;/em&gt; of something whose truth lives somewhere else. The cache is never the source of truth. It's a bet — a bet that the copy still matches the original closely enough to be useful.&lt;/p&gt;

&lt;p&gt;That's the entire distinction. Everything else follows from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this distinction has teeth
&lt;/h2&gt;

&lt;p&gt;If you store something in memory, there is no concept of "going stale." A variable in your JavaScript runtime doesn't expire. &lt;code&gt;let x = 5&lt;/code&gt; doesn't have a TTL. The value is correct until you explicitly change it, because nothing else has authority over it.&lt;/p&gt;

&lt;p&gt;If you store something in a cache, staleness is not a bug — it's the entire reason the structure exists in the first place. A cache without a staleness model isn't a cache. It's just memory pretending to be a cache, and it will eventually be wrong in a way nobody designed for.&lt;/p&gt;

&lt;p&gt;This is the part frontend engineers skip past constantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  React state vs React Query cache
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; is memory. The component owns the value. There is no external source of truth this value is trying to track. If you call &lt;code&gt;setCount(5)&lt;/code&gt;, count is 5. Permanently, until you change it again. No invalidation model needed because there's nothing to invalidate against.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useQuery&lt;/code&gt; from React Query is cache. The value it holds is not owned by your component — it's a snapshot of data that lives on a server, fetched at some point in the past. React Query doesn't trust that snapshot indefinitely. That's why &lt;code&gt;staleTime&lt;/code&gt; exists. That's why &lt;code&gt;refetchOnWindowFocus&lt;/code&gt; exists. That's why every cached entry has an implicit question attached to it: &lt;em&gt;is this still true?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The mental model shift: memory answers "what is the value." Cache answers "what was the value, and how confident am I that it still is."&lt;/p&gt;




&lt;h2&gt;
  
  
  Redux vs RTK Query — the same codebase, two philosophies
&lt;/h2&gt;

&lt;p&gt;This gets interesting because Redux Toolkit lets you do both inside the same store, and a lot of teams don't realize they're switching philosophies mid-app.&lt;/p&gt;

&lt;p&gt;A Redux slice for UI state — &lt;code&gt;isModalOpen&lt;/code&gt;, &lt;code&gt;selectedTab&lt;/code&gt;, &lt;code&gt;currentStep&lt;/code&gt; — is memory. Nobody asks if &lt;code&gt;isModalOpen&lt;/code&gt; is "stale." It's either true or false because your application set it directly. There's no external reality it's trying to mirror.&lt;/p&gt;

&lt;p&gt;RTK Query's cached API responses, sitting in the same Redux store, are cache. They have &lt;code&gt;lastFetchedAt&lt;/code&gt; timestamps. They have invalidation tags. They get refetched when a mutation runs. The data isn't owned — it's borrowed from the server and tracked with suspicion.&lt;/p&gt;

&lt;p&gt;Look in your Redux DevTools sometime and notice: some slices have no concept of expiry. Others — usually the RTK Query slices — carry metadata about freshness. That's not an implementation detail. That's two different architectural categories sharing one storage mechanism.&lt;/p&gt;




&lt;h2&gt;
  
  
  The browser HTTP cache is the purest example
&lt;/h2&gt;

&lt;p&gt;This one is almost a textbook case because the browser is explicit about it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cache-Control: max-age=3600&lt;/code&gt; is the browser literally encoding: "this is not the truth, it's a copy, and the copy is good enough for 3600 seconds." After that, the browser doesn't trust its own copy anymore. It revalidates.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ETag&lt;/code&gt; headers exist purely to answer "is my copy still accurate?" without re-downloading the whole resource. That's the cache asking the source of truth a cheap question instead of assuming.&lt;/p&gt;

&lt;p&gt;There's no equivalent concept for, say, a value stored in &lt;code&gt;localStorage&lt;/code&gt; that you wrote and control entirely. &lt;code&gt;localStorage.setItem('theme', 'dark')&lt;/code&gt; doesn't go stale. It's memory. You own it. Nothing external invalidates it except you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why conflating them breaks systems
&lt;/h2&gt;

&lt;p&gt;The actual cost of treating cache as memory: you stop asking "is this still true?" You start trusting a copy as if it were the source.&lt;/p&gt;

&lt;p&gt;This is how stale UI bugs happen — a cached value gets treated like owned state, gets read without revalidation, and the user sees data that diverged from reality minutes ago.&lt;/p&gt;

&lt;p&gt;The actual cost of treating memory as cache: you start adding unnecessary invalidation logic, staleness checks, and revalidation triggers to a value that was never uncertain in the first place. This is how you end up with &lt;code&gt;useEffect&lt;/code&gt; chains trying to "sync" a piece of state that should have just been derived directly.&lt;/p&gt;

&lt;p&gt;Both mistakes come from the same root cause: not asking, at design time, &lt;em&gt;who owns this value, and is there an external truth it needs to track.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The question to ask before choosing a storage mechanism
&lt;/h2&gt;

&lt;p&gt;Before reaching for &lt;code&gt;useState&lt;/code&gt;, Redux, React Query, SWR, localStorage, or any cache layer — ask one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does an external source of truth exist for this value, separate from my application?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If yes — server data, another service's state, anything outside your control — you need a cache. Build in staleness, invalidation, and revalidation from day one. Don't treat the first fetch as permanent truth.&lt;/p&gt;

&lt;p&gt;If no — UI state, derived values, anything your application fully owns — you need memory. Don't add expiry logic to something that can't go stale. Don't "invalidate" a value nothing external can contradict.&lt;/p&gt;

&lt;p&gt;Most frontend bugs involving "stale data" or "state out of sync" trace back to answering this question wrong at the start — usually by treating cache like memory, and trusting a copy longer than the copy deserves to be trusted.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this was useful, I write about frontend architecture and build systems. Follow for more.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Vite's cold and warm builds cost the same. That's not a flex.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Tue, 16 Jun 2026 10:50:34 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/vites-cold-and-warm-builds-cost-the-same-thats-not-a-flex-3848</link>
      <guid>https://dev.to/khaledmsalem/vites-cold-and-warm-builds-cost-the-same-thats-not-a-flex-3848</guid>
      <description>&lt;p&gt;Look at this:&lt;/p&gt;

&lt;p&gt;Vite cold build:  111ms&lt;br&gt;
Vite warm build:  110ms&lt;/p&gt;

&lt;p&gt;One millisecond difference.&lt;/p&gt;

&lt;p&gt;That's not impressive performance.&lt;br&gt;
That's an architecture with no memory between runs.&lt;/p&gt;

&lt;p&gt;When cold and warm cost the same —&lt;br&gt;
your tool isn't remembering anything.&lt;br&gt;
It's rebuilding from scratch every single time.&lt;br&gt;
Whether you ran it 10 seconds ago or 10 days ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  What memory looks like
&lt;/h2&gt;

&lt;p&gt;Ionify cold:  50ms&lt;br&gt;
Ionify warm:  20ms&lt;/p&gt;

&lt;p&gt;60% faster on warm. Because the engine remembered.&lt;/p&gt;

&lt;p&gt;The persistent dependency graph loaded from disk.&lt;br&gt;
The CAS checked which artifacts are still valid.&lt;br&gt;
Only changed modules were retransformed.&lt;/p&gt;

&lt;p&gt;The gap between cold and warm isn't a cache trick.&lt;br&gt;
It's the measurable value of a build engine that persists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this compounds across a team
&lt;/h2&gt;

&lt;p&gt;On a single machine, the difference is 80ms.&lt;br&gt;
Noticeable. Not life-changing.&lt;/p&gt;

&lt;p&gt;Across a team with CI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every CI run rebuilds what dev already built&lt;/li&gt;
&lt;li&gt;Every machine reconstructs what another machine already knew&lt;/li&gt;
&lt;li&gt;Every deploy sends the full bundle to the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because no tool in the chain remembers anything.&lt;/p&gt;

&lt;p&gt;Ionify's persistent graph and CAS are shared.&lt;br&gt;
What one machine computes, another can reuse.&lt;br&gt;
The dependency contracts published at analysis time&lt;br&gt;
are consumed by dev, build, CI, and federation — identically.&lt;/p&gt;

&lt;p&gt;One machine's work stops being wasted.&lt;/p&gt;




&lt;h2&gt;
  
  
  The browser side
&lt;/h2&gt;

&lt;p&gt;After a bugfix deploy:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Re-download&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vite&lt;/td&gt;
&lt;td&gt;~479 KB (full bundle)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ionify&lt;/td&gt;
&lt;td&gt;~10 KB (entry chunk only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because Ionify knows which chunk changed.&lt;br&gt;
Vite knows a build happened.&lt;/p&gt;




&lt;p&gt;Ionify is open source and production-ready.&lt;br&gt;
Validated on 11,000+ module, 25,000+ dependency apps.&lt;/p&gt;

&lt;p&gt;→ github.com/ionifyjs/ionify ⭐&lt;br&gt;
Drop your warm build time in the comments 👇&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Vite Unified Toolchains IS Brilliant, But Is it Enough for Frontend 2026</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Tue, 09 Jun 2026 18:11:45 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/vite-unified-toolchains-is-brilliant-but-is-it-enough-for-frontend-2026-5b7h</link>
      <guid>https://dev.to/khaledmsalem/vite-unified-toolchains-is-brilliant-but-is-it-enough-for-frontend-2026-5b7h</guid>
      <description>&lt;h1&gt;
  
  
  Unified Toolchains Are Solving Yesterday's Problem
&lt;/h1&gt;

&lt;p&gt;Vite 8 is a huge engineering achievement.&lt;/p&gt;

&lt;p&gt;A unified stack built around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;Rolldown&lt;/li&gt;
&lt;li&gt;OXC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reduces inconsistencies and improves collaboration across layers.&lt;/p&gt;

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

&lt;p&gt;But I think we're asking the wrong question.&lt;/p&gt;

&lt;p&gt;The problem was never:&lt;/p&gt;

&lt;p&gt;"Which compiler should we use?"&lt;/p&gt;

&lt;p&gt;The problem was:&lt;/p&gt;

&lt;p&gt;"Why does the same application have multiple realities?"&lt;/p&gt;

&lt;p&gt;For years frontend developers have accepted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development Reality&lt;/li&gt;
&lt;li&gt;Build Reality&lt;/li&gt;
&lt;li&gt;CI Reality&lt;/li&gt;
&lt;li&gt;Production Reality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then we act surprised when bugs appear after deployment.&lt;/p&gt;

&lt;p&gt;The industry keeps optimizing execution.&lt;/p&gt;

&lt;p&gt;But execution isn't the root problem.&lt;/p&gt;

&lt;p&gt;Authority is.&lt;/p&gt;

&lt;p&gt;At Ionify Engine, we're exploring a different model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One Graph.&lt;/li&gt;
&lt;li&gt;One CAS.&lt;/li&gt;
&lt;li&gt;One Dependency Authority.&lt;/li&gt;
&lt;li&gt;One Pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The objective isn't simply replacing a compiler or bundler.&lt;br&gt;
The objective is making dev and production consume the same artifact identities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because every time two systems disagree about ownership, exports, dependencies, or module identity, you create a new place where production-only bugs can hide.&lt;/p&gt;

&lt;p&gt;The future of frontend tooling may not belong to the fastest compiler.&lt;/p&gt;

&lt;p&gt;It may belong to the system with the fewest realities.&lt;/p&gt;

&lt;p&gt;⭐ github.com/ionifyjs/ionify &lt;/p&gt;

&lt;p&gt;🌐 ionify.cloud&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>architecture</category>
      <category>discuss</category>
    </item>
    <item>
      <title>You are Using Module Federation Wrong.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Mon, 08 Jun 2026 12:01:11 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/you-are-using-module-federation-wrong-oc3</link>
      <guid>https://dev.to/khaledmsalem/you-are-using-module-federation-wrong-oc3</guid>
      <description>&lt;p&gt;Let’s look at the actual workflow of an enterprise team using Module Federation today.&lt;/p&gt;

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

&lt;p&gt;Team A works on the authentication micro-frontend. Team B works on the dashboard. Both pull a shared UI component library. &lt;/p&gt;

&lt;p&gt;Team A updates a component, builds it, and deploys it independently. Awesome. Runtime code sharing works exactly as advertised. &lt;/p&gt;

&lt;p&gt;But what happens on Team B's machine five minutes later? &lt;/p&gt;

&lt;p&gt;When they pull the latest changes, their local bundler or CI container still has to reconstruct the dependency graph, re-optimize the incoming packages, and re-transform the modules from absolute zero. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Micro-Frontend Tax
&lt;/h3&gt;

&lt;p&gt;Traditional Module Federation only shares &lt;strong&gt;runtime code&lt;/strong&gt;. &lt;br&gt;
But behind the scenes, your build systems are completely isolated, stateless, and blind to each other. You are decoupled at runtime, but your pipelines are stuck running the exact same redundant computations over and over again.&lt;/p&gt;

&lt;p&gt;In 2026, sharing JavaScript chunks shouldn't be the ultimate goal. Code sharing should just be a natural consequence of a smart, federated architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter Ionify: Sharing the Brain, Not Just the Code
&lt;/h3&gt;

&lt;p&gt;We built &lt;strong&gt;Ionify Federation&lt;/strong&gt; to change how teams scale large-scale applications. We shifted the baseline from ephemeral local compilation to &lt;strong&gt;Federated Build Intelligence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With Ionify, when one team handles a compilation step, the entire ecosystem instantly inherits the result. &lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
// Traditional MF: Shares the built chunk at runtime
// Ionify Federation: Shares the actual computation state

- No More Re-Transformation: Teams see remote transformations and share them. If Team A transformed a module, Team B hydrates it instantly in milliseconds.
- No More Re-Optimization: Teams see remote dependency optimizations. Graph resolution and package splitting happen exactly once in human history.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>architecture</category>
      <category>ionify</category>
    </item>
    <item>
      <title>RTK Query and React Query look identical from the outside.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sun, 07 Jun 2026 19:11:33 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/rtk-query-and-react-query-look-identical-from-the-outside-43gn</link>
      <guid>https://dev.to/khaledmsalem/rtk-query-and-react-query-look-identical-from-the-outside-43gn</guid>
      <description>&lt;p&gt;RTK Query and React Query look identical from the outside.&lt;/p&gt;

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

&lt;p&gt;Both cache API responses.&lt;br&gt;
Both deduplicate in-flight requests.&lt;br&gt;
Both have cache invalidation.&lt;br&gt;
Both give you loading/error states.&lt;/p&gt;

&lt;p&gt;So developers ask: which one should I use?&lt;/p&gt;

&lt;p&gt;Wrong question. The right question is: where do you believe server state belongs?&lt;/p&gt;

&lt;p&gt;━━━━━━━━━━━━━━━&lt;/p&gt;

&lt;p&gt;RTK Query's architectural answer:&lt;/p&gt;

&lt;p&gt;Server state is just another slice of your Redux store.&lt;/p&gt;

&lt;p&gt;The cache lives inside Redux. That means:&lt;br&gt;
→ Your API responses are inspectable in Redux DevTools&lt;br&gt;
→ Your server state and client state share the same update cycle&lt;br&gt;
→ Cache invalidation is a Redux action under the hood&lt;br&gt;
→ You can select server cache the same way you select any Redux state&lt;/p&gt;

&lt;p&gt;RTK Query treats the server as an external mutation source — but the data, once fetched, becomes Redux state. You own it now.&lt;/p&gt;

&lt;p&gt;━━━━━━━━━━━━━━━&lt;/p&gt;

&lt;p&gt;React Query's architectural answer:&lt;/p&gt;

&lt;p&gt;Server state doesn't belong in your state manager at all.&lt;/p&gt;

&lt;p&gt;The cache is completely isolated — React Query has no idea Redux exists, and Redux has no idea React Query exists. They operate in different layers entirely.&lt;/p&gt;

&lt;p&gt;React Query treats every cached value as a temporary observation. It never fully trusts local state. staleTime is not an optimization — it's a philosophical statement: "I don't own this truth, I'm just borrowing it."&lt;/p&gt;

&lt;p&gt;━━━━━━━━━━━━━━━&lt;/p&gt;

&lt;p&gt;One layer deeper — the invalidation model:&lt;/p&gt;

&lt;p&gt;RTK Query invalidation: tag-based.&lt;br&gt;
You define tags on queries and mutations. When a mutation runs, it invalidates matching tags. Deterministic. Explicit. You're in control.&lt;/p&gt;

&lt;p&gt;React Query invalidation: time + intent based.&lt;br&gt;
Data goes stale automatically after staleTime. You also manually call queryClient.invalidateQueries(). The cache itself knows it might be wrong — you don't have to tell it.&lt;/p&gt;

&lt;p&gt;RTK Query: "invalid until I say otherwise"&lt;br&gt;
React Query: "valid until time proves otherwise"&lt;/p&gt;

&lt;p&gt;━━━━━━━━━━━━━━━&lt;/p&gt;

&lt;p&gt;So which one?&lt;/p&gt;

&lt;p&gt;If your app is Redux-first and server state needs to be part of a unified state model → RTK Query. The integration is seamless and DevTools alone are worth it.&lt;/p&gt;

&lt;p&gt;If server state is its own concern — separate from UI state, separate from client decisions → React Query. The isolation is a feature, not a limitation.&lt;/p&gt;

&lt;p&gt;The confusion happens because both solve the same surface problem.&lt;br&gt;
The difference is where they believe the truth should live.&lt;/p&gt;

&lt;p&gt;Redux says: truth lives in the store.&lt;br&gt;
React Query says: truth lives on the server. We just cache it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your build tool gets dumber every deploy! Time To Move On</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sun, 07 Jun 2026 10:33:19 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/your-build-tool-gets-dumber-every-deploy-time-to-move-on-o38</link>
      <guid>https://dev.to/khaledmsalem/your-build-tool-gets-dumber-every-deploy-time-to-move-on-o38</guid>
      <description>&lt;p&gt;Think about what happens to Vite's knowledge of your app after a build.&lt;/p&gt;

&lt;p&gt;It disappears.&lt;/p&gt;

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

&lt;p&gt;The dependency graph it just constructed — gone. The module relationships it traced — gone. The vendor boundaries it calculated — gone. Next build, it starts over. From zero. With no memory of anything, it just learned.&lt;/p&gt;

&lt;p&gt;Every deploy, Vite is as smart as it was on day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What compounding build intelligence actually means
&lt;/h2&gt;

&lt;p&gt;Ionify persists the dependency graph across runs. That's the headline.&lt;/p&gt;

&lt;p&gt;But the deeper thing is what that persistence enables over time.&lt;/p&gt;

&lt;p&gt;The graph accumulates &lt;strong&gt;route history&lt;/strong&gt; — which modules get requested together, which vendor packages appear on which routes, which closures are shared across navigations. That knowledge doesn't come from a single build. It comes from observing your app across many builds, many runs, many real navigations.&lt;/p&gt;

&lt;p&gt;A build tool that runs once knows your code.&lt;br&gt;
A build engine that persists knows your app.&lt;/p&gt;

&lt;p&gt;The difference shows up in the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vendor requests:               46 → 9   (−80%)
Bytes transferred:           1.8MB → 420KB (−76%)
Parse time saved:              120ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's not from a smarter algorithm on a single run. That's from route-aware vendor packs built from accumulated graph intelligence — knowing which dependencies actually appear together at runtime, not just at build time.&lt;/p&gt;


&lt;h2&gt;
  
  
  The cold vs warm gap tells the real story
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ionify cold:  52ms   (full precompression included)
Ionify warm:  30ms   (CAS hits + graph reuse)

Vite cold:   111ms   (no precompression)
Vite warm:   110ms   (full retransform, every time)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Vite's cold and warm are nearly identical. That's not a coincidence — it's the architecture. Vite has nothing to reuse, so every run costs roughly the same.&lt;/p&gt;

&lt;p&gt;Ionify's warm is 42% faster than cold. That gap is the value of memory. And it grows as the graph stabilizes.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why CAS changes the multi-machine equation
&lt;/h2&gt;

&lt;p&gt;Content-addressable storage means artifacts are keyed by what they contain, not when they were produced.&lt;/p&gt;

&lt;p&gt;Practical consequence: if your CI machine and your dev machine built the same module with the same inputs — they produced the same CAS key. One machine's work is reusable by the other.&lt;/p&gt;

&lt;p&gt;Traditional tools: every machine rebuilds independently.&lt;br&gt;
Ionify: the team shares a build cache without any explicit cache configuration.&lt;/p&gt;


&lt;h2&gt;
  
  
  The browser side nobody talks about
&lt;/h2&gt;

&lt;p&gt;After any deploy, Vite users re-download ~479 KB — vendor code, React runtime, shared utilities, all of it — because the build output changed and the browser has no way to know what part changed.&lt;/p&gt;

&lt;p&gt;Ionify splits output by cache lifetime:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chunk&lt;/th&gt;
&lt;th&gt;Changes when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chunk-vendor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You update a dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chunk-shared&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You refactor shared utilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chunk-entry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After a bugfix deploy: &lt;code&gt;chunk-entry&lt;/code&gt; changes (10 KB). Everything else stays cached.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Users re-download 10 KB. Not 479 KB.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  This isn't "Vite but faster"
&lt;/h2&gt;

&lt;p&gt;Vite optimizes a single build run.&lt;br&gt;
Ionify optimizes the relationship between runs.&lt;/p&gt;

&lt;p&gt;That's a different problem. And it compounds.&lt;/p&gt;

&lt;p&gt;→ github.com/ionifyjs/ionify ⭐&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://ionify.cloud/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fionify.cloud%2Flogo%2FIonify-dark-logo.png" height="1018" class="m-0" width="763"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://ionify.cloud/" rel="noopener noreferrer" class="c-link"&gt;
            Ionify — The Build Engine That Doesn’t Start Over
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Ionify is a Rust-powered build engine with a persistent dependency graph and content-addressable cache. One unified pipeline for dev, build, and test.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fionify.cloud%2Ffavicon.svg" width="64" height="64"&gt;
          ionify.cloud
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>react</category>
      <category>architecture</category>
      <category>ionify</category>
    </item>
    <item>
      <title>Frontend Build Tools Speed is a Placebo</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Thu, 04 Jun 2026 10:36:48 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/frontend-build-tools-speed-is-a-placebo-2opm</link>
      <guid>https://dev.to/khaledmsalem/frontend-build-tools-speed-is-a-placebo-2opm</guid>
      <description>&lt;p&gt;One of the hardest engineering decisions is to walk away from Vite. Let’s be honest, the current Build Tools have a real Engineering Leakage.&lt;/p&gt;

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

&lt;p&gt;You change a single interface in a shared Monorepo package. You run your build script. Your terminal proudly flashes: &lt;code&gt;CACHE HIT&lt;/code&gt; or &lt;code&gt;Tasks: 12 cached, 1 executed&lt;/code&gt;. You get a hit of dopamine. You think your tooling is smart.&lt;/p&gt;

&lt;p&gt;But then you check the network tab, the CI logs, and the cloud compute bill. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s a placebo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional build caches don’t understand the semantic layout of your application. They cache files based on file-system timestamps or naive input hashing. The moment a core graph node shifts—even by a byte—the entire stateless machine panics, invalidates the downstream cache, and triggers a full, brutal &lt;strong&gt;Cold Start&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cache vs. Memory Illusion
&lt;/h3&gt;

&lt;p&gt;Caching is trying to save a temporary snapshot of a dumb process.&lt;br&gt;
Memory is an inherent property of an intelligent architecture.&lt;/p&gt;

&lt;p&gt;Traditional tools (Webpack, Vite, and even modern stateless wrappers) store artifacts but throw away the &lt;strong&gt;Dependency Graph&lt;/strong&gt; after the run. They have no past memory. They don't track route history, dependency depth, or runtime co-requests over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Ionify’s CAS Rewrites the Rules
&lt;/h3&gt;

&lt;p&gt;We built &lt;strong&gt;Ionify&lt;/strong&gt; because we got tired of faking build speed with file-system caches. &lt;/p&gt;

&lt;p&gt;Ionify treats your codebase as a permanent,&lt;strong&gt;Persistence Graph&lt;/strong&gt; driven by true &lt;strong&gt;Content-Addressable Storage (CAS)&lt;/strong&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't look at &lt;em&gt;when&lt;/em&gt; a file changed; it looks at &lt;em&gt;what&lt;/em&gt; the file actually contains. &lt;/li&gt;
&lt;li&gt;If a module's cryptographic identity matches a verified artifact in the CAS, the transformation is skipped completely—even across different branches, and even across different machines on your team via Ionify Cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is absolute determinism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ionify Warm Build:&lt;/strong&gt; 30ms (CAS hits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite Warm Build:&lt;/strong&gt; 110ms (Full re-transformation, every single time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop using tools that need to be reminded of who you are on every run. Give your infrastructure a memory layer that actually computes valid work exactly once.&lt;/p&gt;

&lt;p&gt;👉 Haven't you asked yourself in 2026 why your build architecture is still this primitive? &lt;/p&gt;

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

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>react</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Our CI Pipeline Kept Rebuilding the Same Graph (And How I Fixed It)</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Mon, 01 Jun 2026 12:47:48 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/why-our-ci-pipeline-kept-rebuilding-the-same-graph-and-how-i-fixed-it-3bf8</link>
      <guid>https://dev.to/khaledmsalem/why-our-ci-pipeline-kept-rebuilding-the-same-graph-and-how-i-fixed-it-3bf8</guid>
      <description>&lt;p&gt;Most frontend teams optimise build speed.&lt;/p&gt;

&lt;p&gt;But few measure how much work is &lt;strong&gt;unnecessarily repeated.&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.amazonaws.com%2Fuploads%2Farticles%2F4k7yc6uz3dhj1gsq31v8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k7yc6uz3dhj1gsq31v8.png" alt=" " width="512" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We ran into this problem while working on larger frontend systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple developers&lt;/li&gt;
&lt;li&gt;multiple apps&lt;/li&gt;
&lt;li&gt;shared dependencies&lt;/li&gt;
&lt;li&gt;constant CI runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On paper, builds were “fast”.&lt;br&gt;
In practice, we were still wasting time&lt;/p&gt;




&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;Our CI pipeline looked healthy.&lt;br&gt;
But we noticed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;small changes triggered large rebuilds&lt;/li&gt;
&lt;li&gt;identical graphs were recomputed across runs&lt;/li&gt;
&lt;li&gt;the same dependencies were resolved repeatedly&lt;/li&gt;
&lt;li&gt;different apps rebuilt overlapping work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing was technically broken.&lt;br&gt;
&lt;strong&gt;But everything was being repeated.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;The issue wasn’t tooling performance.&lt;/p&gt;

&lt;p&gt;It was an &lt;strong&gt;assumption&lt;/strong&gt;:&lt;br&gt;
Every build starts from zero.&lt;/p&gt;

&lt;p&gt;That assumption forces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;graph rediscovery&lt;/li&gt;
&lt;li&gt;redundant transformations&lt;/li&gt;
&lt;li&gt;duplicate dependency resolution&lt;/li&gt;
&lt;li&gt;no shared memory between runs&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;At small scale → acceptable. &lt;br&gt;
At team scale → expensive.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Measuring the Cost
&lt;/h2&gt;

&lt;p&gt;** Even without precise metrics, the pattern was clear:**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identical work across CI runs&lt;/li&gt;
&lt;li&gt;repeated build steps across developers&lt;/li&gt;
&lt;li&gt;overlapping dependency graphs across apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speed improvements didn’t solve it.&lt;br&gt;
They only made repetition faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift
&lt;/h2&gt;

&lt;p&gt;Instead of asking:&lt;br&gt;
How do we make builds faster?&lt;/p&gt;

&lt;p&gt;We asked:&lt;br&gt;
Why are we rebuilding valid work at all?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution Approach
&lt;/h2&gt;

&lt;p&gt;The key idea is simple:&lt;/p&gt;

&lt;p&gt;Treat the build as a persistent graph system, not a disposable process.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remembering valid work&lt;/li&gt;
&lt;li&gt;tracking changes at graph level&lt;/li&gt;
&lt;li&gt;invalidating only what changed&lt;/li&gt;
&lt;li&gt;reusing work across runs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where Ionify Fits
&lt;/h2&gt;

&lt;p&gt;This is the problem Ionify is designed to solve.&lt;/p&gt;

&lt;p&gt;Not by optimising execution speed, but by eliminating unnecessary repetition.&lt;/p&gt;




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

&lt;p&gt;Most build discussions focus on speed.&lt;/p&gt;

&lt;p&gt;At scale, the real problem is wasted work.&lt;/p&gt;

&lt;p&gt;And wasted work isn’t a performance problem.&lt;/p&gt;

&lt;p&gt;It’s a memory problem.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>discuss</category>
      <category>ionify</category>
    </item>
    <item>
      <title>Kicking a dead horse at the speed of light doesn't make it run.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sat, 30 May 2026 19:43:12 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/kicking-a-dead-horse-at-the-speed-of-light-doesnt-make-it-run-472i</link>
      <guid>https://dev.to/khaledmsalem/kicking-a-dead-horse-at-the-speed-of-light-doesnt-make-it-run-472i</guid>
      <description>&lt;p&gt;Let’s be honest. The frontend community has a new silver bullet, and it’s called Rust. &lt;/p&gt;

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

&lt;p&gt;Every week, there’s a new wrapper, a new linter, or a new bundler boasting "100x faster execution because of Rust." But here is the uncomfortable truth about the 2026 frontend tooling ecosystem: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kicking a dead horse at the speed of light doesn't make it run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing your compiler or transformer in Rust (like Oxc or SWC) is fantastic for raw compute. But if your underlying architecture is still stateless—meaning it completely destroys the dependency graph after every run and starts from absolute zero on the next boot—you haven't solved the bottleneck. You just made a dumb architecture execute faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Problem: Stateless Amnesia
&lt;/h3&gt;

&lt;p&gt;Traditional tools like Vite and Webpack suffer from "Past Amnesia". Every time you switch a branch, pull from main, or clear a cache, the tool treats your project like a stranger. &lt;/p&gt;

&lt;p&gt;At scale, in enterprise monorepos with thousands of modules, this is a massive tax on engineering velocity.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Ionify Paradigm Shift
&lt;/h3&gt;

&lt;p&gt;We built &lt;strong&gt;Ionify&lt;/strong&gt; because we realized that the next bottleneck isn't execution speed—it's &lt;strong&gt;system memory&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Ionify uses a native &lt;strong&gt;Persistence Graph&lt;/strong&gt; backed by &lt;strong&gt;Content-Addressable Storage (CAS)&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We don’t cache; we remember.&lt;/strong&gt; - If a module or its dependencies haven't changed, the compiled artifact is immutable and permanently verified. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmark proves it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ionify Warm Build:&lt;/strong&gt; 30ms (CAS hits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite Warm Build:&lt;/strong&gt; 110ms (Full re-transformation every single time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the scale of +11K module, the benchmark becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ionify Warm Build:&lt;/strong&gt; 200ms (CAS hits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite Warm Build:&lt;/strong&gt; 2.2s (Full re-transformation every single time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop celebrating tools that just do useless repeated work faster. It’s 2026. Your build system should have a brain.&lt;/p&gt;

&lt;p&gt;👉 Check out how memory beats raw speed: &lt;a href="https://ionify.cloud/" rel="noopener noreferrer"&gt;ionify.cloud&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>discuss</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a build engine that remembers. Here's what happened.</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Sat, 23 May 2026 18:11:23 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/i-built-a-build-engine-that-remembers-heres-what-happened-5642</link>
      <guid>https://dev.to/khaledmsalem/i-built-a-build-engine-that-remembers-heres-what-happened-5642</guid>
      <description>&lt;p&gt;We've been optimizing the wrong thing.&lt;/p&gt;

&lt;p&gt;Vite made builds fast. Webpack made builds structured. Everyone declared victory and moved on.&lt;/p&gt;

&lt;p&gt;But nobody asked the question that's been bothering me for years:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does every build start from zero?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not as a philosophical exercise. As a real engineering problem.&lt;/p&gt;

&lt;p&gt;Your dependency graph didn't change. Your packages didn't change. 90% of what your build tool is about to do — it already did last week. On this machine. And on every other machine on your team.&lt;/p&gt;

&lt;p&gt;But it rebuilds anyway because it doesn't remember.&lt;/p&gt;




&lt;h2&gt;
  
  
  So I built something that does.
&lt;/h2&gt;

&lt;p&gt;Ionify is a frontend build engine with one core architectural difference from every tool you've used before:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dependency graph persists.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not in RAM. Persisted and shared — across runs, across dev and build, across every machine on your team.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Every other tool
run build
  → build a graph in memory
  → use it
  → destroy it  ❌

// Ionify
run build
  → load graph from disk
  → update only what changed
  → save back  ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one change cascades into everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually changes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Content-addressable artifacts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every output is stored by its content hash. If the content didn't change, the artifact already exists. No rebuild. No retransform. Just reuse.&lt;/p&gt;

&lt;p&gt;This works across machines. Your CI doesn't recompute what your dev already computed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route-aware vendor packs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The persistent graph accumulates route history. It knows which dependencies appear together at runtime. It groups them accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before vendor packs:
  requests: 46    bytes: 1.8 MB    parse time: baseline

After:
  requests:  9    bytes: 420 KB    parse time saved: 120ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;80% fewer requests. 76% fewer bytes. Not from HTTP/2 tricks. From the engine knowing your app better than a single build ever could.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-compressed output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vite's model: emit raw assets, let nginx compress at request time. At Brotli quality 11, that's too slow for per-request use, so servers use br:4–6. That's 15–30% larger than maximum compression.&lt;/p&gt;

&lt;p&gt;Ionify emits &lt;code&gt;.br&lt;/code&gt; and &lt;code&gt;.gz&lt;/code&gt; sidecars at build time at maximum quality (br:11, gz:9). The server reads pre-built bytes directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request-time CPU:      zero
Brotli quality:        br:11 (maximum)
Warm rebuild cost:     3ms (CAS hit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The benchmark that surprised me
&lt;/h2&gt;

&lt;p&gt;After fixing three parallelization issues in the build pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
  cold: 144ms

  ↓ Fix 1: parallel CAS restore (br+gz)    → -35ms
  ↓ Fix 2: eager hash hint                 → -10ms
  ↓ Fix 3: parallel collectFilesRecursive  → -47ms net

After:
  cold: 52ms ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the full comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ionify cold:  52ms   (full precompression included)
Vite cold:   111ms   (no precompression at all)

Ionify warm:  30ms   (CAS hits + sidecar reuse)
Vite warm:   110ms   (full retransform every time)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Warm at 30ms isn't clever caching. It's the engine verifying previous work is still valid and reusing it. Vite retransforms everything because it has no way to know.&lt;/p&gt;




&lt;h2&gt;
  
  
  On the browser side
&lt;/h2&gt;

&lt;p&gt;When you ship a bugfix, a Vite user's browser re-downloads the full 479 KB bundle. The vendor code, the React runtime, the shared utilities — all of it. Because from Vite's perspective, a build happened and the output changed.&lt;/p&gt;

&lt;p&gt;On the second visit after any deploy: &lt;strong&gt;Ionify transfers ~10 KB. Vite transfers 479 KB.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The architectural table nobody shows you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Webpack&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;Persistent graph&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAS artifacts&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route-aware packs&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same engine dev+build&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-compressed (br:11)&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analysis lifetime&lt;/td&gt;
&lt;td&gt;per-run&lt;/td&gt;
&lt;td&gt;per-run&lt;/td&gt;
&lt;td&gt;persistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gets faster over time&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What this isn't
&lt;/h2&gt;

&lt;p&gt;This isn't "Vite is bad." Vite is an excellent escape from Webpack and HMR optimization since that time. But frontend grew into a system, and systems need engines, not just tools.&lt;/p&gt;

&lt;p&gt;Tools are built for a moment. Engines are built for systems.&lt;br&gt;
That's where Ionify lives.&lt;/p&gt;

&lt;p&gt;Single machine → modest improvement.&lt;br&gt;
Multi-machine CI + shared deps + many routes → the gap becomes structural.&lt;/p&gt;

&lt;p&gt;At that point, you're not comparing tools. You're comparing whether your build infrastructure has memory or not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;→ &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ionifyjs/ionify" rel="noopener noreferrer"&gt;github.com/ionify/&lt;/a&gt; ⭐&lt;br&gt;
→ &lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://ionify.cloud" rel="noopener noreferrer"&gt;ionify.cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this resonates, a star goes a long way. And I'd genuinely love to hear if you've hit the same wall.&lt;/p&gt;

&lt;p&gt;What's your current build time on a warm run?&lt;/p&gt;

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

</description>
      <category>javascript</category>
      <category>frontendchallenge</category>
      <category>tooling</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
