<?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: Amrendra kumar</title>
    <description>The latest articles on DEV Community by Amrendra kumar (@codewithamrendra).</description>
    <link>https://dev.to/codewithamrendra</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%2F3968073%2F51855c65-0998-463f-9412-420b0c50358f.png</url>
      <title>DEV Community: Amrendra kumar</title>
      <link>https://dev.to/codewithamrendra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codewithamrendra"/>
    <language>en</language>
    <item>
      <title>Improving Your React Code by Avoiding Hooks Mistakes</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Tue, 30 Jun 2026 09:21:14 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/improving-your-react-code-by-avoiding-hooks-mistakes-2h4j</link>
      <guid>https://dev.to/codewithamrendra/improving-your-react-code-by-avoiding-hooks-mistakes-2h4j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick Summary:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In 2026, planning to design a website or apps is quite easier. One more thing is came which named as &lt;strong&gt;React hooks&lt;/strong&gt;, that also discussed in this Blog, Mainly react hooks are came from three sources like as: missing dependencies from component also breaks render component for 'useffect'.&lt;/p&gt;

&lt;p&gt;Most teams reach for &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt; before diagnosing the actual re-render cause, which adds complexity without fixing the bug. Fix the dependency array first, profile second, memoize last.&lt;/p&gt;

&lt;p&gt;After this, you will be able to triage a Hooks-related bug report without guessing, and decide whether &lt;code&gt;useMemo&lt;/code&gt; is solving a real problem or just adding noise.&lt;/p&gt;

&lt;p&gt;For deeper patterns on component design, see these &lt;a href="https://amrendra-blog.vercel.app/category/react" rel="noopener noreferrer"&gt;React architecture patterns&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Breaks: The 3 Hook Mistakes That Cause Production Bugs
&lt;/h2&gt;

&lt;p&gt;These three are responsible for the majority of Hooks bugs that make it past code review. Each one looks correct at a glance and only breaks under specific render conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stale closures from missing dependencies
&lt;/h3&gt;

&lt;p&gt;A closure inside &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, or &lt;code&gt;useMemo&lt;/code&gt; captures variables at the time the function was created, not at call time. Omit a dependency and the callback keeps using the value from the render where it was defined.&lt;br&gt;
&lt;/p&gt;

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

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;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;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// always logs 0 — stale closure&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt; &lt;span class="c1"&gt;// missing 'count' dependency&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&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;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is not to silence the lint rule. Add &lt;code&gt;count&lt;/code&gt; to the dependency array, or switch to the functional update form (&lt;code&gt;setCount(c =&amp;gt; c + 1)&lt;/code&gt;) if the effect doesn't actually need to read &lt;code&gt;count&lt;/code&gt; directly. As per the React docs (react.dev, 2025), the dependency array exists specifically to keep effect callbacks synchronized with the latest render's values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional hook calls breaking render order
&lt;/h3&gt;

&lt;p&gt;React tracks hook state by call order, not by name. A hook called behind a condition shifts every hook after it on the next render where the condition changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong — breaks hook order when `id` becomes empty&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Select a game&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setGame&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;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* fetch game */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GameView&lt;/span&gt; &lt;span class="na"&gt;game&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Correct — hooks run unconditionally, logic moves after&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setGame&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;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&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="cm"&gt;/* fetch game */&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Select a game&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GameView&lt;/span&gt; &lt;span class="na"&gt;game&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;eslint-plugin-react-hooks&lt;/code&gt; (npmjs.com) in CI, not just locally. This class of bug often passes a quick manual test and fails only under specific prop transitions in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unbounded useEffect re-runs without cleanup
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;useEffect&lt;/code&gt; that sets a timer, subscription, or listener needs a cleanup function. Skip it and every re-run stacks another active subscription on top of the last one.&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="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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;doSomething&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// prevents leaks and duplicate timers&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In codebases without this pattern enforced, a single screen with three subscribing effects can leak dozens of listeners after a few minutes of normal navigation. The symptom is rarely a crash — it's degraded performance that only shows up after the app has been open a while, which is why it survives code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  useCallback vs useMemo vs Doing Nothing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ux2r7ypodbt489agso6.webp" 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%2F5ux2r7ypodbt489agso6.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most Hooks guides tell you to memoize without explaining when memoization is wasted work. React re-runs a component function on every render regardless of memoization; &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt; only stop &lt;em&gt;downstream&lt;/em&gt; re-renders or recomputation, and they cost a comparison check every render to do it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Function passed to a &lt;code&gt;React.memo&lt;/code&gt; child&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useCallback&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Without it, a new function reference breaks the child's prop equality check every render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expensive computation (sort, filter, transform) on large data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useMemo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skips recomputation when inputs are unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function or value used only inside the same component&lt;/td&gt;
&lt;td&gt;Neither&lt;/td&gt;
&lt;td&gt;No downstream consumer benefits from referential stability; the memoization check costs more than the work it saves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object or array literal passed as a prop&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;useMemo&lt;/code&gt; (or restructure props)&lt;/td&gt;
&lt;td&gt;Object literals are a new reference every render, defeating &lt;code&gt;React.memo&lt;/code&gt; on the receiving component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple inline handler with no memoized child&lt;/td&gt;
&lt;td&gt;Neither&lt;/td&gt;
&lt;td&gt;Premature memoization here is dead weight&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In large React codebases, unnecessary &lt;code&gt;useMemo&lt;/code&gt;/&lt;code&gt;useCallback&lt;/code&gt; calls are one of the more common causes of code review churn — they signal a performance concern that was never measured, and they make diffs harder to read for no measurable gain.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Diagnose a Re-render Problem Before You Memoize Anything
&lt;/h2&gt;

&lt;p&gt;Don't memoize speculatively. Open React DevTools, enable the Profiler tab, and record an interaction that feels slow. The flame graph shows which components re-rendered and why DevTools labels the trigger as props, state, context, or parent re-render.&lt;/p&gt;

&lt;p&gt;If the trigger is "parent re-rendered" and the child's props are unchanged primitives, wrap the child in &lt;code&gt;React.memo&lt;/code&gt; first. Only add &lt;code&gt;useCallback&lt;/code&gt;/&lt;code&gt;useMemo&lt;/code&gt; upstream if the Profiler still shows the child re-rendering after that change that's the signal a new reference is breaking the memo check, not a guess.&lt;/p&gt;

&lt;p&gt;This sequence (profile, then memo the child, then memoize the source if needed) catches the actual bottleneck instead of scattering &lt;code&gt;useCallback&lt;/code&gt; across a component tree based on intuition. Teams that skip the Profiler step typically end up memoizing the wrong layer and still ship the original slowdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do React 19's Compiler and Actions Change These Mistakes?
&lt;/h2&gt;

&lt;p&gt;The React Compiler (react.dev, 2025) auto-memoizes components and values at build time, which removes the need for manual &lt;code&gt;useCallback&lt;/code&gt;/&lt;code&gt;useMemo&lt;/code&gt; in most cases when the compiler is enabled. It does not fix stale closures or conditional hook calls those are still runtime correctness bugs the compiler can't infer away.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useActionState&lt;/code&gt; and form Actions, introduced in React 19, replace a chunk of the manual &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;useEffect&lt;/code&gt; pattern previously used for form submission and pending states, which removes one entire category of dependency-array mistakes by removing the effect itself. The mistakes in this guide are not obsolete; they shift toward correctness (closures, hook order) rather than performance, since the compiler increasingly handles the performance side.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What is the most common mistake with useEffect?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Omitting a dependency that the callback actually reads, which produces a stale closure. The effect keeps using the value from whichever render it was created in, not the latest one, until something forces a re-run with the correct dependency array.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Why does my useEffect cause an infinite loop?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually an object or array literal in the dependency array, or a state setter inside the effect that updates a value the effect also depends on. Each render creates a new reference, which the dependency comparison treats as changed, triggering another run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Can I call a Hook inside a condition or loop?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. React tracks hook state by call order across renders, and a conditional call shifts that order whenever the condition changes. Call hooks unconditionally at the top level and move conditional logic after the hook calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What is a stale closure in React Hooks?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A closure that captured a variable's value from a previous render and never updates, because it's missing from the dependency array. It shows up as a callback that logs or uses an outdated value even though the component has re-rendered with new state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. When should I use useCallback vs useMemo?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;useCallback&lt;/code&gt; when passing a function to a memoized child component. Use &lt;code&gt;useMemo&lt;/code&gt; for expensive computations or to stabilize object/array references passed as props. Skip both when nothing downstream depends on referential stability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amrendra Kumar is a software engineer and technical writer at &lt;a href="https://amrendra-blog.vercel.app" rel="noopener noreferrer"&gt;Code with Amrendra&lt;/a&gt;, where he covers React, Next.js, AI Agents, SaaS architecture, and cloud infrastructure. He has written 15+ technical articles on frontend engineering, system design, and modern web development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/amrendra-reactdev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://github.com/AmrendraCodes" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>api</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Stop Wasting Renders: How to Handle Complex REST APIs in Your Frontend</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Tue, 16 Jun 2026 11:52:03 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/stop-wasting-renders-how-to-handle-complex-rest-apis-in-your-frontend-37fk</link>
      <guid>https://dev.to/codewithamrendra/stop-wasting-renders-how-to-handle-complex-rest-apis-in-your-frontend-37fk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;React REST API render optimization&lt;/strong&gt; comes down to three problems: mixing server state with client state in &lt;code&gt;useState&lt;/code&gt;, firing network requests on every component mount because &lt;code&gt;staleTime&lt;/code&gt; defaults to 0, and creating request waterfalls through nested component data fetching.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article covers the three architectural patterns that cause render waste when integrating &lt;a href="https://amrendra-blog.vercel.app/" rel="noopener noreferrer"&gt;REST APIs in React&lt;/a&gt;, and the exact production fixes for each. You will come away knowing how to separate server state from client state, configure TanStack Query's cache correctly, and eliminate request waterfalls without restructuring your entire component tree. &lt;/p&gt;

&lt;p&gt;If you are also thinking about how component architecture affects render behaviour at scale, &lt;a href="https://amrendra-blog.vercel.app/blog/react-component-systems" rel="noopener noreferrer"&gt;Building Reusable React Component Systems at Scale&lt;/a&gt; covers the structural decisions that compound with API integration patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Source of React Render Waste
&lt;/h2&gt;

&lt;p&gt;Most render waste does not come from missing &lt;code&gt;React.memo&lt;/code&gt;. It comes from storing server state in local &lt;code&gt;useState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Server state and client state have fundamentally different update requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server state&lt;/strong&gt;: async, shared across components, stale after time, needs background refresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client state&lt;/strong&gt;: synchronous, local to a component or feature, user-driven&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you put an API response into &lt;code&gt;useState&lt;/code&gt;, every parent re-render, every context update, every sibling state change can cause your fetch cycle to reset or your component to re-render with data it already had. There is no deduplication, no shared cache, no background refresh. Separating these two concerns is the foundational step before any library decision.&lt;/p&gt;

&lt;p&gt;For teams building on Next.js, the same separation applies at the framework level — &lt;a href="https://amrendra-blog.vercel.app/blog/scalable-nextjs" rel="noopener noreferrer"&gt;Building Scalable Next.js Applications&lt;/a&gt; covers how to structure data fetching across App Router without coupling server state to component lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;useEffect&lt;/code&gt; + &lt;code&gt;useState&lt;/code&gt; for API Calls Doesn't Scale
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This pattern breaks at scale&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUser&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;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// AbortController required to handle race conditions&lt;/span&gt;
    &lt;span class="c1"&gt;// when userId changes faster than the previous request resolves&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&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;Two components mounting simultaneously with the same &lt;code&gt;userId&lt;/code&gt; fire two separate network requests. No shared cache. No deduplication. &lt;/p&gt;

&lt;p&gt;Manual &lt;code&gt;AbortController&lt;/code&gt; just to handle race conditions. This is not a pattern to optimise with memoization it is a pattern to replace above a certain complexity threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Waterfalls: How to Diagnose and Fix Them
&lt;/h2&gt;

&lt;p&gt;A request waterfall is when network requests execute sequentially that could run in parallel. Two patterns cause this in React.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern 1 : Dependent queries:&lt;/strong&gt; fetch user, then fetch posts using the user ID. The posts request cannot start until the user request resolves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern 2 : Nested component waterfalls:&lt;/strong&gt; parent fetches data, conditionally renders a child, child fetches its own data. The child's request cannot start until the parent finishes rendering.&lt;/p&gt;

&lt;p&gt;Both are diagnosable in Chrome DevTools Network tab. Look for sequential same-domain requests with no timing overlap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing Dependent Queries with &lt;code&gt;useQueries&lt;/code&gt;
&lt;/h3&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;useQueries&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="nf"&gt;useQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="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;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;queryFn&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;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;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;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;queryFn&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;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// enabled prevents firing until userId exists,&lt;/span&gt;
        &lt;span class="c1"&gt;// but does NOT create a waterfall both queries&lt;/span&gt;
        &lt;span class="c1"&gt;// are registered and run in parallel on mount&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;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;userQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postsQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As per TanStack Query v5 docs (tanstack.com/query/latest/docs), &lt;code&gt;useQueries&lt;/code&gt; runs all queries in parallel by default. The &lt;code&gt;enabled&lt;/code&gt; flag controls whether a query fires  not whether it waits for another query to complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing Nested Component Waterfalls with Prefetching
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Route loader  executes before the component tree renders&lt;/span&gt;
&lt;span class="k"&gt;export&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;loader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&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;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getQueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Prefetch child data so it's already in cache&lt;/span&gt;
  &lt;span class="c1"&gt;// when the child component mounts and calls useQuery&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prefetchQuery&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;comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postId&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The child component calls &lt;code&gt;useQuery&lt;/code&gt; for comments and gets a cache hit immediately. The network request is gone from the component render cycle entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;staleTime&lt;/code&gt; and &lt;code&gt;gcTime&lt;/code&gt;: The Two Config Values That Matter Most
&lt;/h2&gt;

&lt;p&gt;The TanStack Query default is &lt;code&gt;staleTime: 0&lt;/code&gt;. This means every component mount is treated as a cache miss and fires a network request. In a dashboard with 10 components reading the same endpoint, that is 10 requests on a single page load.&lt;/p&gt;

&lt;p&gt;Configure &lt;code&gt;staleTime&lt;/code&gt; per data type based on how often that data actually changes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Type&lt;/th&gt;
&lt;th&gt;Recommended &lt;code&gt;staleTime&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;&lt;code&gt;gcTime&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Reasoning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User profile&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;10 minutes&lt;/td&gt;
&lt;td&gt;Changes rarely mid-session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live feed / notifications&lt;/td&gt;
&lt;td&gt;10-30 seconds&lt;/td&gt;
&lt;td&gt;1 minute&lt;/td&gt;
&lt;td&gt;Needs to stay current&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config / reference data&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;24 hours&lt;/td&gt;
&lt;td&gt;Almost never changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search results&lt;/td&gt;
&lt;td&gt;30 seconds&lt;/td&gt;
&lt;td&gt;2 minutes&lt;/td&gt;
&lt;td&gt;User expects fresh results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Source: TanStack Query v5 docs (tanstack.com/query/latest/docs). Configuration values based on production patterns. Last updated: June 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For multi-tenant SaaS products where different tenants have different data freshness requirements, this configuration approach changes significantly — &lt;a href="https://amrendra-blog.vercel.app/blog/saas-architecture-scale" rel="noopener noreferrer"&gt;Designing Multi-Tenant SaaS Platforms for Scale&lt;/a&gt; covers the database and routing strategies that inform how per-tenant cache configuration should be structured.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 5 minutes: safe default for stable data&lt;/span&gt;
      &lt;span class="na"&gt;gcTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// 10 minutes: keep in memory after unmount&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;// Override per query where freshness requirements differ&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;notifications&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;fetchNotifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 15 seconds: this data must stay current&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gcTime&lt;/code&gt; controls when &lt;em&gt;inactive&lt;/em&gt; cached data gets garbage collected  not how long it is considered fresh. Setting &lt;code&gt;gcTime&lt;/code&gt; lower than &lt;code&gt;staleTime&lt;/code&gt; means data is garbage collected before it goes stale, which defeats the cache entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  SWR vs TanStack Query: Which One for Your Use Case
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SWR v2&lt;/th&gt;
&lt;th&gt;TanStack Query v5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size (gzipped)&lt;/td&gt;
&lt;td&gt;~4.2KB&lt;/td&gt;
&lt;td&gt;~13KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request deduplication&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;staleTime&lt;/code&gt; control&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full, per-query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutation handling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;useSWRMutation&lt;/code&gt; (basic)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;useMutation&lt;/code&gt; (full)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevTools&lt;/td&gt;
&lt;td&gt;NO (official)&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useSuspenseQueries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript inference&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent (v5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimistic updates&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Built-in with rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Source: swr.vercel.app, tanstack.com/query/latest/docs. Bundle sizes from bundlephobia.com. Last updated: June 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision rule:&lt;/strong&gt; Use SWR if you need minimal bundle size and simple fetch-cache-revalidate patterns with Next.js. Use TanStack Query if you have mutations, dependent queries, DevTools requirements, or need &lt;code&gt;useSuspenseQueries&lt;/code&gt; for waterfall elimination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimistic Updates Without Breaking Data Integrity
&lt;/h2&gt;

&lt;p&gt;The trap with optimistic updates is rollback handling. If the mutation fails, you must revert the UI without a flash or a stale cache entry surviving into the next render cycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedPost&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;updatePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;onMutate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Cancel outgoing refetches to avoid overwriting&lt;/span&gt;
    &lt;span class="c1"&gt;// the optimistic update with stale server data&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancelQueries&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;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Snapshot the previous value  this is the rollback target&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getQueryData&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply the optimistic change to the cache immediately&lt;/span&gt;
    &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setQueryData&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&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="nx"&gt;previousPost&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Restore snapshot on failure&lt;/span&gt;
    &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setQueryData&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousPost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;onSettled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Always refetch after success or error&lt;/span&gt;
    &lt;span class="c1"&gt;// to sync cache with actual server truth&lt;/span&gt;
    &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&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;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;onSettled&lt;/code&gt; runs after both success and error, ensuring the cache eventually reflects server truth regardless of what happened during the optimistic phase. Skipping &lt;code&gt;onSettled&lt;/code&gt; leaves the cache in an optimistic state permanently if the server silently rejects the mutation&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Checklist: React REST API Integration
&lt;/h2&gt;

&lt;p&gt;Before shipping any React feature that calls a REST API, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server state lives in TanStack Query or SWR, not in &lt;code&gt;useState&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;staleTime&lt;/code&gt; is configured per query based on actual data freshness requirements — not left at the default of 0&lt;/li&gt;
&lt;li&gt;Network tab shows no sequential same-domain requests that could run in parallel&lt;/li&gt;
&lt;li&gt;Mutations use &lt;code&gt;onError&lt;/code&gt; rollback — not just &lt;code&gt;onSuccess&lt;/code&gt; handling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;QueryClient&lt;/code&gt; is instantiated per request in SSR contexts, never at module level (module-level &lt;code&gt;QueryClient&lt;/code&gt; leaks data across users in server environments)&lt;/li&gt;
&lt;li&gt;Query cancellation is wired for queries that depend on rapidly-changing props like search input&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;React render waste from REST APIs is an architectural problem, not a component-level one. The fix starts with separating server state from client state, then configuring your cache to match actual data freshness requirements, then eliminating waterfalls before they reach production. &lt;/p&gt;

&lt;p&gt;Every pattern in this article is production-tested and applies directly to dashboards, SaaS products, and admin panels hitting REST endpoints at scale.&lt;/p&gt;

&lt;p&gt;If this helped, follow on GitHub for production React patterns and annotated code samples: &lt;a href="https://github.com/AmrendraCodes" rel="noopener noreferrer"&gt;github.com/AmrendraCodes&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Amrendra Kumar is a software engineer and technical writer at &lt;a href="https://amrendra-blog.vercel.app/" rel="noopener noreferrer"&gt;Code with Amrendra&lt;/a&gt;, where he covers React, Next.js, AI Agents, SaaS architecture, and cloud infrastructure. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/amrendra-reactdev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://github.com/AmrendraCodes" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>frontend</category>
      <category>performance</category>
      <category>react</category>
    </item>
    <item>
      <title>React vs Next.js: What Should Developers Choose?</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:00:19 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/react-vs-nextjs-what-should-developers-choose-2ljc</link>
      <guid>https://dev.to/codewithamrendra/react-vs-nextjs-what-should-developers-choose-2ljc</guid>
      <description>&lt;h1&gt;
  
  
  React + Vite vs Next.js: Production Decision Guide
&lt;/h1&gt;

&lt;p&gt;Last Updated: June 2026&lt;br&gt;
Last Reviewed: June 2026&lt;br&gt;
Author: Amrendra Kumar&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Summary Box&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React + Vite vs Next.js&lt;/strong&gt; is not a tooling debate. It is an architecture decision. React + Vite fits client-heavy apps where routing, APIs, rendering, and deployment stay separate. Next.js fits product surfaces that need server rendering, metadata, image handling, route handlers, and React Server Components. Pick Vite when you want control. Pick Next.js when rendering strategy affects revenue, SEO, or infrastructure shape.&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This guide helps you decide whether a production React project should use React + Vite or Next.js. The decision comes down to rendering, routing, infrastructure ownership, caching, and team boundaries. React + Vite gives you a smaller frontend surface with fewer framework rules. Next.js gives you a full-stack React runtime with server rendering, file-based routing, route handlers, and React Server Components.&lt;/p&gt;

&lt;p&gt;For more React architecture patterns, read the &lt;a href="https://amrendra-blog.vercel.app/category/react" rel="noopener noreferrer"&gt;React performance guides&lt;/a&gt;. After this guide, you should be able to defend the choice in a technical design review.&lt;/p&gt;
&lt;h2&gt;
  
  
  React + Vite vs Next.js: The Actual Comparison
&lt;/h2&gt;

&lt;p&gt;React + Vite is a frontend app setup. Next.js is a React framework with routing, rendering, server functions, image handling, and deployment conventions.&lt;/p&gt;

&lt;p&gt;The wrong comparison is “which is faster?” Vite usually gives a faster local dev server. Next.js gives more production primitives. Those are different axes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;React + Vite&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App type&lt;/td&gt;
&lt;td&gt;Client-rendered SPA by default&lt;/td&gt;
&lt;td&gt;Full-stack React framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing&lt;/td&gt;
&lt;td&gt;Add React Router or TanStack Router&lt;/td&gt;
&lt;td&gt;File-based routing included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendering&lt;/td&gt;
&lt;td&gt;CSR by default&lt;/td&gt;
&lt;td&gt;CSR, SSR, SSG, streaming, RSC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server code&lt;/td&gt;
&lt;td&gt;Separate backend required&lt;/td&gt;
&lt;td&gt;Route Handlers and Server Actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO control&lt;/td&gt;
&lt;td&gt;Manual setup&lt;/td&gt;
&lt;td&gt;Metadata API and server rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image handling&lt;/td&gt;
&lt;td&gt;Manual CDN or library setup&lt;/td&gt;
&lt;td&gt;Built-in Image component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Static hosting works well&lt;/td&gt;
&lt;td&gt;Server or platform runtime often needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team fit&lt;/td&gt;
&lt;td&gt;Frontend and backend split&lt;/td&gt;
&lt;td&gt;Product teams shipping vertical slices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: React docs, Next.js docs, Vite docs. Last updated: June 2026.&lt;/p&gt;

&lt;p&gt;In shipped dashboards, I usually keep React + Vite when the app sits behind auth and depends on API latency anyway. I reach for Next.js when the first document response, metadata, caching, or content route matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Rendering Models Differ: CSR, SSR, SSG, RSC
&lt;/h2&gt;

&lt;p&gt;Rendering decides where HTML gets created and how much JavaScript the browser must hydrate.&lt;/p&gt;

&lt;p&gt;React + Vite gives you client-side rendering unless you add a meta-framework or custom SSR pipeline. Next.js gives rendering choices at the route level. That is useful, but it also creates more failure modes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rendering model&lt;/th&gt;
&lt;th&gt;React + Vite&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Best use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSR&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Dashboards, internal tools, authenticated apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR&lt;/td&gt;
&lt;td&gt;Custom setup&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Request-specific pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSG&lt;/td&gt;
&lt;td&gt;Build pipeline required&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Blogs, docs, marketing pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSC&lt;/td&gt;
&lt;td&gt;Not available by default&lt;/td&gt;
&lt;td&gt;App Router default&lt;/td&gt;
&lt;td&gt;Server-first UI with smaller client bundles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: React Server Components docs, Next.js App Router docs. Last updated: June 2026.&lt;/p&gt;

&lt;p&gt;React Server Components render ahead of time in a server environment separate from the client app or SSR server, according to React docs. Next.js App Router uses Server Components by default, which changes where data fetching and component execution happen.&lt;/p&gt;
&lt;h3&gt;
  
  
  The RSC Client Boundary Gotcha
&lt;/h3&gt;

&lt;p&gt;The biggest App Router mistake is putting &lt;code&gt;'use client'&lt;/code&gt; too high in the tree. That turns every imported child module into client code.&lt;/p&gt;

&lt;p&gt;Bad boundary:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&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;ProductList&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;./ProductList&lt;/span&gt;&lt;span class="dl"&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;getProducts&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;@/lib/products&lt;/span&gt;&lt;span class="dl"&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductsPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This is wrong because Client Components cannot directly run server-only data access.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&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;getProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Correct boundary:&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;ProductListClient&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;./ProductListClient&lt;/span&gt;&lt;span class="dl"&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;getProducts&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;@/lib/products&lt;/span&gt;&lt;span class="dl"&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductsPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Runs on the server. Database tokens and private APIs stay off the client bundle.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&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;getProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&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="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductListClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: $&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React docs define &lt;code&gt;'use client'&lt;/code&gt; as a boundary in the module dependency tree. In practice, that means the directive does not only affect one component. It affects the imported subtree below that file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison: React + Vite vs Next.js
&lt;/h2&gt;

&lt;p&gt;Next.js ships product-level features. React + Vite asks you to choose those parts yourself.&lt;/p&gt;

&lt;p&gt;That is not a weakness. It is the core trade-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing
&lt;/h3&gt;

&lt;p&gt;React + Vite needs a router library. React Router and TanStack Router both work well for serious apps. You get explicit route objects, loader patterns if your router supports them, and fewer framework file conventions.&lt;/p&gt;

&lt;p&gt;Next.js gives file-based routing through the App Router. Layouts, nested segments, loading states, and route-level server rendering live inside the framework.&lt;/p&gt;

&lt;p&gt;Choose Vite when your team wants explicit route config. Choose Next.js when route folders, layouts, and server-first routing reduce glue code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Fetching
&lt;/h3&gt;

&lt;p&gt;React + Vite usually pairs with TanStack Query, SWR, Apollo, or custom fetch wrappers. Data fetching happens in the browser unless your backend prepares data separately.&lt;/p&gt;

&lt;p&gt;Next.js lets Server Components fetch data before the client bundle runs. Route Handlers handle custom HTTP endpoints inside the app directory. Server Actions can handle mutations from forms and Client Components.&lt;/p&gt;

&lt;p&gt;The trade-off is debugging complexity. Server and client execution now share one repo but not one runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Optimization
&lt;/h3&gt;

&lt;p&gt;React + Vite does not include image transformation. You usually rely on Cloudinary, Imgix, a CDN, or build-time image tooling.&lt;/p&gt;

&lt;p&gt;Next.js includes the Image component. The docs state that it can serve correctly sized images, modern formats, layout stability, lazy loading, and remote image resizing through the Next.js server.&lt;/p&gt;

&lt;p&gt;For ecommerce, blogs, and landing pages, this can reduce layout shift work. For authenticated SaaS dashboards, image handling rarely decides the framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Endpoints
&lt;/h3&gt;

&lt;p&gt;React + Vite does not include backend endpoints. You deploy APIs separately through Express, Fastify, NestJS, Lambda, Cloudflare Workers, or another backend.&lt;/p&gt;

&lt;p&gt;Next.js supports Route Handlers in the app directory using the Web Request and Response APIs. That is useful for webhooks, lightweight BFF endpoints, auth callbacks, and internal product APIs.&lt;/p&gt;

&lt;p&gt;For large backend domains, do not bury core business services inside Next.js routes. Keep domain APIs separate and use Next.js as the product edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Cost and Infrastructure Trade-offs
&lt;/h2&gt;

&lt;p&gt;React + Vite is cheap to deploy because it can compile into static assets. Next.js cost depends on which features you use.&lt;/p&gt;

&lt;p&gt;A static Next.js app can be cheap. A Next.js app using SSR, image transformation, server functions, middleware, and cache invalidation needs runtime capacity.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;React + Vite&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Cost at scale&lt;/th&gt;
&lt;th&gt;Server requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static CDN&lt;/td&gt;
&lt;td&gt;Natural fit&lt;/td&gt;
&lt;td&gt;Works for static export limits&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;No app server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;td&gt;Best platform fit&lt;/td&gt;
&lt;td&gt;Can rise with SSR and image usage&lt;/td&gt;
&lt;td&gt;Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS S3 + CloudFront&lt;/td&gt;
&lt;td&gt;Natural fit&lt;/td&gt;
&lt;td&gt;Needs extra setup for SSR&lt;/td&gt;
&lt;td&gt;Low for static assets&lt;/td&gt;
&lt;td&gt;No for static, yes for SSR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node server&lt;/td&gt;
&lt;td&gt;Usually backend only&lt;/td&gt;
&lt;td&gt;Common for self-hosting&lt;/td&gt;
&lt;td&gt;Depends on traffic&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serverless functions&lt;/td&gt;
&lt;td&gt;API layer only&lt;/td&gt;
&lt;td&gt;Route-level server execution&lt;/td&gt;
&lt;td&gt;Can spike with request volume&lt;/td&gt;
&lt;td&gt;Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Overkill for many SPAs&lt;/td&gt;
&lt;td&gt;Useful for platform teams&lt;/td&gt;
&lt;td&gt;Operational cost high&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: Vite deployment docs, Next.js deployment docs, AWS hosting patterns. Last updated: June 2026.&lt;/p&gt;

&lt;p&gt;In architecture reviews, I separate framework cost from platform cost. Next.js is not automatically expensive. It becomes expensive when every route becomes request-rendered and image processing sits on the app platform instead of a tuned CDN path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision Matrix: Which to Choose for Your Project Type
&lt;/h2&gt;

&lt;p&gt;Pick the framework based on product shape, not developer preference.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project archetype&lt;/th&gt;
&lt;th&gt;Better choice&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Internal admin dashboard&lt;/td&gt;
&lt;td&gt;React + Vite&lt;/td&gt;
&lt;td&gt;SEO does not matter and APIs already exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authenticated SaaS app&lt;/td&gt;
&lt;td&gt;React + Vite or Next.js&lt;/td&gt;
&lt;td&gt;Use Vite for app shell, Next.js for marketing plus app routes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content-heavy blog&lt;/td&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;td&gt;SSG, metadata, routing, and image handling matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecommerce storefront&lt;/td&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;td&gt;Server rendering, product metadata, and image handling affect conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time collaboration app&lt;/td&gt;
&lt;td&gt;React + Vite&lt;/td&gt;
&lt;td&gt;Client state, sockets, and backend services dominate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-tenant B2B platform&lt;/td&gt;
&lt;td&gt;Next.js with separate backend&lt;/td&gt;
&lt;td&gt;Product routes and server components help, but domain APIs stay separate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: React docs, Next.js docs, production architecture patterns. Last updated: June 2026.&lt;/p&gt;

&lt;p&gt;A practical hybrid is common: Next.js for public pages, pricing, docs, SEO pages, and account entry points. React + Vite for the logged-in workspace if the app behaves like a desktop product.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Migrate a React SPA to Next.js
&lt;/h2&gt;

&lt;p&gt;Do not migrate only because Next.js is popular. Migrate when the current SPA creates measurable product or engineering pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger 1: SEO Depends on Rendered Content
&lt;/h3&gt;

&lt;p&gt;If crawlers need product descriptions, docs pages, canonical metadata, structured data, or localized content, a pure SPA becomes fragile.&lt;/p&gt;

&lt;p&gt;You can patch metadata with prerendering, but once content routes multiply, Next.js usually reduces custom infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger 2: The Backend-for-Frontend Layer Keeps Growing
&lt;/h3&gt;

&lt;p&gt;A Vite app often starts with direct API calls. Later, the frontend needs auth shaping, request aggregation, feature flags, analytics enrichment, and webhook-adjacent endpoints.&lt;/p&gt;

&lt;p&gt;That is the point where Route Handlers or a dedicated BFF becomes useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger 3: Initial Load Carries Too Much Client JavaScript
&lt;/h3&gt;

&lt;p&gt;If the first route downloads code for data fetching, formatting, layout decisions, and non-interactive content, server-first rendering can cut client work.&lt;/p&gt;

&lt;p&gt;React Server Components help because server-only UI does not add to the client JavaScript bundle in the same way Client Components do.&lt;/p&gt;

&lt;h2&gt;
  
  
  React 19 Changes the Equation
&lt;/h2&gt;

&lt;p&gt;React 19 makes the framework decision less about React itself and more about the runtime around React.&lt;/p&gt;

&lt;p&gt;React 19 added stable APIs around Actions, Server Functions, document metadata, and Server Components support through frameworks. React docs also clarify that there is no directive for Server Components. &lt;code&gt;'use server'&lt;/code&gt; marks Server Functions, not Server Components.&lt;/p&gt;

&lt;p&gt;This matters because plain React + Vite does not automatically give you RSC infrastructure. The React features exist, but you need a framework or integration layer to wire bundling, transport, routing, and server execution.&lt;/p&gt;

&lt;p&gt;Next.js already integrates those pieces in App Router. That gives it an advantage for teams that want server-first React now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remix and TanStack Start as Alternatives
&lt;/h3&gt;

&lt;p&gt;Next.js is not the only route.&lt;/p&gt;

&lt;p&gt;Remix focuses on web fundamentals, nested routing, server loaders, actions, and progressive behavior. React Router framework mode now carries much of that direction.&lt;/p&gt;

&lt;p&gt;TanStack Start targets type-safe full-stack React with TanStack Router, server functions, and streaming patterns. It fits teams already invested in TanStack Query and router primitives.&lt;/p&gt;

&lt;p&gt;The decision is not Next.js or nothing. The real decision is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPA toolchain: React + Vite&lt;/li&gt;
&lt;li&gt;Full-stack React framework: Next.js, Remix, or TanStack Start&lt;/li&gt;
&lt;li&gt;Custom platform: React with your own SSR, routing, and backend edge&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Is React + Vite better than Next.js?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;React + Vite is better for client-heavy apps, internal tools, and products with a separate backend. Next.js is better when routing, server rendering, metadata, image handling, or React Server Components affect the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Should I use Next.js for every React project?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Next.js adds server and framework behavior that many apps do not need. A dashboard behind auth with existing APIs often ships faster with React + Vite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is Vite only for small React apps?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Vite can handle serious production SPAs. The limit is not Vite. The limit is whether your product needs server rendering, route-level data loading, metadata, and backend endpoints inside the React app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Does Next.js replace a backend?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Next.js can host Route Handlers and Server Actions, but it should not hide complex domain services. Payments, permissions, billing, reporting, and integrations often deserve dedicated backend services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Does React 19 make Vite equal to Next.js?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. React 19 adds framework-facing capabilities, but Vite alone does not provide RSC transport, server routing, or request rendering. You still need framework integration for those features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. When should I migrate from Vite to Next.js?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Migrate when SEO, server-rendered content, metadata, route-level caching, or BFF endpoints become recurring problems. Do not migrate only for local developer speed or framework popularity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Is Next.js more expensive to deploy than Vite?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Often, yes, when you use SSR, image processing, middleware, and server functions heavily. A Vite SPA on static hosting usually has a simpler cost model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Can I use React + Vite for SaaS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Many SaaS workspaces fit React + Vite well because they run behind login and depend on APIs. Use Next.js when the SaaS also needs public pages, docs, pricing, SEO, and server-rendered entry routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion + CTA
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;React + Vite vs Next.js&lt;/strong&gt; comes down to ownership. React + Vite keeps the frontend thin, explicit, and easy to host. Next.js gives you a server-aware React runtime with routing, rendering, metadata, image handling, Route Handlers, and React Server Components.&lt;/p&gt;

&lt;p&gt;Choose React + Vite for dashboards, internal tools, real-time workspaces, and apps with a strong backend boundary.&lt;/p&gt;

&lt;p&gt;Choose Next.js for content-heavy products, ecommerce, docs, marketing pages, SEO routes, and product surfaces where the first HTML response matters.&lt;/p&gt;

&lt;p&gt;If this helped, follow Amrendra Kumar on GitHub for more production-grade React and Next.js patterns: &lt;a href="https://github.com/AmrendraCodes" rel="noopener noreferrer"&gt;https://github.com/AmrendraCodes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;&lt;br&gt;
Amrendra Kumar is a software engineer and technical writer at&lt;br&gt;
&lt;a href="https://amrendra-blog.vercel.app" rel="noopener noreferrer"&gt;Code with Amrendra&lt;/a&gt;, where he&lt;br&gt;
covers React, Next.js, AI Agents, SaaS architecture, and cloud&lt;br&gt;
infrastructure. He has written 200+ technical articles on frontend&lt;br&gt;
engineering, system design, and modern web development.&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/amrendra-reactdev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; |&lt;br&gt;
&lt;a href="https://github.com/AmrendraCodes" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

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