<?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>Next.js showing old data after a save? Here are 10 checks, in order, that find the cause fast.</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Tue, 03 Nov 2026 10:11:26 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/nextjs-showing-old-data-after-a-save-here-are-10-checks-in-order-that-find-the-cause-fast-18kn</link>
      <guid>https://dev.to/codewithamrendra/nextjs-showing-old-data-after-a-save-here-are-10-checks-in-order-that-find-the-cause-fast-18kn</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m" class="crayons-story__hidden-navigation-link"&gt;Next.js Cache Not Working? A 10-Step Checklist for Next.js 15 and 16&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/codewithamrendra" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3968073%2F51855c65-0998-463f-9412-420b0c50358f.png" alt="codewithamrendra profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/codewithamrendra" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amrendra kumar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amrendra kumar
                
                
              
              &lt;div id="story-author-preview-content-4968291" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/codewithamrendra" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3968073%2F51855c65-0998-463f-9412-420b0c50358f.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amrendra kumar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m" id="article-link-4968291"&gt;
          Next.js Cache Not Working? A 10-Step Checklist for Next.js 15 and 16
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/performance"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;performance&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Next.js Cache Not Working? A 10-Step Checklist for Next.js 15 and 16</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Tue, 03 Nov 2026 10:08:41 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m</link>
      <guid>https://dev.to/codewithamrendra/nextjs-cache-not-working-a-10-step-checklist-for-nextjs-15-and-16-4a5m</guid>
      <description>&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;p&gt;You save some data. The database says yes. The page still shows the old data. If your &lt;strong&gt;&lt;a href="https://www.codewithamrendra.in/" rel="noopener noreferrer"&gt;Next.js cache is not working&lt;/a&gt;&lt;/strong&gt;, Next.js is usually doing what you told it to do, just on an old copy of the page that you forgot about. A Next.js page can live in &lt;strong&gt;four places at once&lt;/strong&gt;: the build, the server, the browser, and a CDN. Below are five quick questions and &lt;strong&gt;10 steps in order&lt;/strong&gt;. They help you find the old copy and fix it, for Next.js 15 and for the new &lt;code&gt;use cache&lt;/code&gt; style in Next.js 16.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Highlight
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do not ask "is the cache broken?" Ask "&lt;strong&gt;which copy&lt;/strong&gt; am I looking at?"&lt;/li&gt;
&lt;li&gt;Since Next.js 15, &lt;code&gt;fetch&lt;/code&gt; is not cached by default. But a page built ahead of time can still keep old data from build day.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;revalidate&lt;/code&gt; does not mean "refresh every N seconds". The &lt;strong&gt;first visitor after the time is over still sees the old page&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In Next.js 16, &lt;code&gt;revalidateTag('x')&lt;/code&gt; with one argument is the old way. Use &lt;code&gt;updateTag&lt;/code&gt; or &lt;code&gt;revalidateTag('x', 'max')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reloading the page fixes an old browser copy. It does nothing for the other three.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Your page lives in 4 places
&lt;/h2&gt;

&lt;p&gt;People say "cache" like it is one thing. It is not. Each place keeps its own copy and gets a new one at a different time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Where&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;What it is&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Gets a new copy when&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Pages made during &lt;code&gt;next build&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The page is revalidated or rebuilt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Data Cache or &lt;code&gt;use cache&lt;/code&gt; entries&lt;/td&gt;
&lt;td&gt;Time runs out, or you clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;The client router cache&lt;/td&gt;
&lt;td&gt;You reload, call &lt;code&gt;router.refresh()&lt;/code&gt;, or its time ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;A CDN or proxy in front of your app&lt;/td&gt;
&lt;td&gt;Its &lt;code&gt;Cache-Control&lt;/code&gt; rules say so&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you fix the wrong place, nothing seems to change. That is why people deploy five times and learn nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  5 quick questions to ask first
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does a full page reload fix it?&lt;/strong&gt; If yes, the browser has an old copy. Go to step 8.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it break only after a production build?&lt;/strong&gt; Go to steps 1, 3 and 4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the new data show up one visit late?&lt;/strong&gt; That is step 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did your save work, but the page did not change?&lt;/strong&gt; Go to steps 6, 7 and 9.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it different on your server and on your public URL?&lt;/strong&gt; Something sits in front of Next.js. Go to step 10.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The 10-step checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Try it on a real build
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;next dev&lt;/code&gt; and production do not act the same. For example, link prefetching only runs in production. Always check the problem on a real build first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .next
npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the problem is gone here, it was only a dev-mode thing. You can stop.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Check which caching style you use
&lt;/h3&gt;

&lt;p&gt;Next.js 16 has two styles, and the docs give different answers for each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&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;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cacheComponents&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="c1"&gt;// if you see this, you use the new style&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No flag? You use the older style, so steps 3 to 5 matter most. With the flag, steps 7 and 9 matter most.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Is your page built at build time?
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;fetch&lt;/code&gt; with no &lt;code&gt;cache&lt;/code&gt; option runs &lt;strong&gt;only once, during &lt;code&gt;next build&lt;/code&gt;&lt;/strong&gt;, if your code reaches it before any request-based function like &lt;code&gt;cookies()&lt;/code&gt; or &lt;code&gt;headers()&lt;/code&gt;. The page is built ahead of time up to that point, so the data stays the same.&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;// app/posts/page.tsx&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;Page&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 once at build time. Later it looks like "cache not working".&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/posts&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostList&lt;/span&gt; &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;await&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="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;If the build-time versus server-time idea is new to you, this short read on &lt;a href="https://www.codewithamrendra.in/resources/blog/future-of-web-development-2026" rel="noopener noreferrer"&gt;how Next.js renders pages at build time and on the server&lt;/a&gt; explains the hybrid model.&lt;/p&gt;

&lt;p&gt;Choose what you want on purpose:&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;// Always fresh&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Fresh at most every 60 seconds&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Look for settings that force caching
&lt;/h3&gt;

&lt;p&gt;One forgotten line can freeze a whole route. Search for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"force-static&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;fetchCache&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;export const revalidate&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;export const dynamic"&lt;/span&gt; app/
&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;   &lt;span class="c1"&gt;// always build ahead of time&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// cache every fetch&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;         &lt;span class="c1"&gt;// cache, never refresh by time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settings in the layouts and pages of one route must work together. If two settings fight, a &lt;code&gt;force-*&lt;/code&gt; option wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;revalidate&lt;/code&gt; does not mean "every N seconds"
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;revalidate = 60&lt;/code&gt; does not rebuild the page every minute. It means: after 60 seconds, the next visitor starts a rebuild.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;

&lt;span class="c1"&gt;// 0s    page is built and saved&lt;/span&gt;
&lt;span class="c1"&gt;// 70s   visitor A -&amp;gt; sees the OLD page, and a rebuild starts in the background&lt;/span&gt;
&lt;span class="c1"&gt;// 71s+  visitor B -&amp;gt; sees the new page&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called stale-while-revalidate: the old page is shown first, and the fresh one is made in the background. If you need fresh data on the very next request, use &lt;code&gt;cache: 'no-store'&lt;/code&gt; or clear the cache yourself (steps 6 and 7).&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Make sure &lt;code&gt;revalidatePath&lt;/code&gt; points to the right page
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;revalidatePath&lt;/code&gt; needs the exact path. A wrong path does nothing, and it does not show any error.&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&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;revalidatePath&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;next/cache&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&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;slug&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="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;savePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// your database call&lt;/span&gt;

  &lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&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="c1"&gt;// one exact page&lt;/span&gt;

  &lt;span class="c1"&gt;// For every page that matches a dynamic route, use the pattern and a type:&lt;/span&gt;
  &lt;span class="c1"&gt;// revalidatePath('/blog/[slug]', 'page')&lt;/span&gt;

  &lt;span class="c1"&gt;// For shared layout data, like a counter in the header:&lt;/span&gt;
  &lt;span class="c1"&gt;// revalidatePath('/blog/[slug]', 'layout')&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docs also say that tags are usually a better choice than paths. They are more exact, and they do not clear more than you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. &lt;code&gt;updateTag&lt;/code&gt; or &lt;code&gt;revalidateTag&lt;/code&gt;? Pick the right one
&lt;/h3&gt;

&lt;p&gt;Many people get stuck here after moving to Next.js 16. The two functions do different jobs.&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&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;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateTag&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;next/cache&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;savePostAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;savePost&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;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;updateTag&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="c1"&gt;// the user sees their own change right away&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Route Handler or webhook&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;onWebhook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;revalidateTag&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// marks it old; the next visit may still see the old data&lt;/span&gt;
  &lt;span class="c1"&gt;// revalidateTag('posts', { expire: 0 }) // expire it right now instead&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;&lt;code&gt;updateTag&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;revalidateTag(tag, 'max')&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Works in&lt;/td&gt;
&lt;td&gt;Server Actions only&lt;/td&gt;
&lt;td&gt;Server Actions and Route Handlers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it does&lt;/td&gt;
&lt;td&gt;Ends the old data right now&lt;/td&gt;
&lt;td&gt;Marks it old, then refreshes on the next visit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use it when&lt;/td&gt;
&lt;td&gt;The user must see their own change&lt;/td&gt;
&lt;td&gt;A small delay is fine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  8. Check the browser's own copy
&lt;/h3&gt;

&lt;p&gt;You fixed the server, but the screen still shows old data? The browser keeps its own copy of pages you visited. Since Next.js 15, pages that are not built ahead of time are kept for 0 seconds by default. Pages built ahead of time, or fully prefetched, can be reused for about 5 minutes.&lt;/p&gt;

&lt;p&gt;Prefetching is what fills this browser copy. If one page has dozens of links, this &lt;a href="https://www.codewithamrendra.in/resources/blog/why-react-apps-slow-down-in-2026" rel="noopener noreferrer"&gt;Next.js performance checklist&lt;/a&gt; also explains why too much prefetching can hurt slow connections.&lt;/p&gt;

&lt;p&gt;If the user stays on the same page after a save, ask the router for a fresh copy.&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;useRouter&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;next/navigation&lt;/span&gt;&lt;span class="dl"&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;SaveButton&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// your Server Action&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// get this page again from the server&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Server Actions can also call &lt;code&gt;refresh()&lt;/code&gt; to get the current page again. The Server Actions guide explains how.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Check your &lt;code&gt;'use cache'&lt;/code&gt; code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cacheLife&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cacheTag&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;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPosts&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;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nf"&gt;cacheLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hours&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1. choose how long to keep it&lt;/span&gt;
  &lt;span class="nf"&gt;cacheTag&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="c1"&gt;// 2. add a name so you can clear it later&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time.&lt;/strong&gt; Add &lt;code&gt;cacheLife&lt;/code&gt; to every cache directive. Without it, a default time is used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tag.&lt;/strong&gt; No tag means you have nothing to give to &lt;code&gt;updateTag&lt;/code&gt; or &lt;code&gt;revalidateTag&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request data.&lt;/strong&gt; Do not use &lt;code&gt;cookies()&lt;/code&gt; or &lt;code&gt;headers()&lt;/code&gt; inside cached code. Read them outside and pass the values in.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  10. Check what sits in front of your app
&lt;/h3&gt;

&lt;p&gt;A CDN or proxy can keep showing the old page even after Next.js made a new one. Compare a response from your server with one from your public URL, and look at the &lt;code&gt;Cache-Control&lt;/code&gt; header on both. The CDN Caching guide in the official docs has more details.&lt;/p&gt;




&lt;h2&gt;
  
  
  How fresh should your data be?
&lt;/h2&gt;

&lt;p&gt;Many stale-data bugs start because nobody decided this. Decide it once for each type of data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;A good place to start&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blog or CMS content&lt;/td&gt;
&lt;td&gt;Keep it for a long time, add a tag, and clear it from a CMS webhook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product list&lt;/td&gt;
&lt;td&gt;One tag per product, and &lt;code&gt;revalidateTag(tag, 'max')&lt;/code&gt; when it changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A user's own settings&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;updateTag&lt;/code&gt; inside the Server Action that saves them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live dashboards&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cache: 'no-store'&lt;/code&gt;, or a very short &lt;code&gt;cacheLife&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anything for one user&lt;/td&gt;
&lt;td&gt;Keep it out of shared caches, and read &lt;code&gt;cookies()&lt;/code&gt; outside cached code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The docs support the first row: keep CMS data for a long time with tags, and refresh it when the content really changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes that waste the most time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploying again to "clear the cache".&lt;/strong&gt; It may or may not work, depending on your host. Find the old copy first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading old answers.&lt;/strong&gt; Next.js 14 cached &lt;code&gt;fetch&lt;/code&gt; by default. Versions 15 and 16 do not. Many forum answers mix these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using &lt;code&gt;revalidatePath('/', 'layout')&lt;/code&gt; for everything.&lt;/strong&gt; It works, but it clears much more than you need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping &lt;code&gt;revalidateTag('x')&lt;/code&gt; with one argument.&lt;/strong&gt; It is the old way in Next.js 16.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never testing a production build.&lt;/strong&gt; Many "cache bugs" only happen in &lt;code&gt;next start&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Why is my Next.js revalidate not working?
&lt;/h3&gt;

&lt;p&gt;Most often it is working as designed. The first visitor after the time is over sees the old page, and that visit starts the rebuild (step 5).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Next.js show old data after I save something?
&lt;/h3&gt;

&lt;p&gt;Either you cleared the wrong path or tag (steps 6 and 7), or the browser still has the old page (step 8).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my page show old data after I deploy?
&lt;/h3&gt;

&lt;p&gt;A page built at build time keeps its build-day data until it is revalidated. Add a time-based or on-demand refresh, or turn caching off for that data (step 3).&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I clear the Next.js cache?
&lt;/h3&gt;

&lt;p&gt;On your computer, delete the &lt;code&gt;.next&lt;/code&gt; folder and build again. In production, do not delete files. Use &lt;code&gt;revalidatePath&lt;/code&gt;, &lt;code&gt;revalidateTag&lt;/code&gt; or &lt;code&gt;updateTag&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does the App Router show old data when I click a link?
&lt;/h3&gt;

&lt;p&gt;That is the browser's router cache. Call &lt;code&gt;router.refresh()&lt;/code&gt; after the save (step 8).&lt;/p&gt;

&lt;h3&gt;
  
  
  Is fetch cached by default in Next.js 15 and 16?
&lt;/h3&gt;

&lt;p&gt;No. But a page can still be built ahead of time at build time, and that keeps the data the same in a similar way (step 3).&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between revalidateTag and updateTag?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;updateTag&lt;/code&gt; ends the old data right now, and it works only in Server Actions. &lt;code&gt;revalidateTag(tag, 'max')&lt;/code&gt; marks the data as old and refreshes it on the next visit.&lt;/p&gt;




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

&lt;p&gt;A stale page just means one of the four places is holding an old copy. Try it on a real build, find the place (build, server, browser or CDN), and use the one fix that belongs to it. Most cases end at step 3, 5 or 8. Keep the five questions handy and ask them before you deploy again. &lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;This article was researched and drafted with AI help, then checked and edited by the author before publishing. Code examples follow the official Next.js docs (versions 15 and 16.2 to 16.3 as of November 2026). Please test them in your own project before you use them.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>performance</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AI Crawlers in robots.txt: Should You Block or Allow Them?</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Tue, 13 Oct 2026 10:59:20 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/ai-crawlers-in-robotstxt-should-you-block-or-allow-them-mj5</link>
      <guid>https://dev.to/codewithamrendra/ai-crawlers-in-robotstxt-should-you-block-or-allow-them-mj5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclosure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm a developer who works with React, Next.js and AI agents, and I offer related SEO and AI services, so I have a business interest in this topic. I used AI tools to help with research and drafting, and I reviewed the content before publishing. Bot names and rules come from official docs and can change, so check them before you deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Quick Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Block training crawlers, allow search crawlers.&lt;/strong&gt; For most sites, the best setup is to disallow bots that collect data for model training (like &lt;code&gt;GPTBot&lt;/code&gt; and &lt;code&gt;ClaudeBot&lt;/code&gt;) and allow bots that power AI search (like &lt;code&gt;OAI-SearchBot&lt;/code&gt; and &lt;code&gt;Claude-SearchBot&lt;/code&gt;). This keeps your content out of training sets but still lets AI tools cite you.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Key Highlights&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AI crawlers fall into three jobs: &lt;strong&gt;training&lt;/strong&gt;, &lt;strong&gt;search&lt;/strong&gt;, and &lt;strong&gt;user-triggered fetch&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each bot has its own &lt;code&gt;User-agent&lt;/code&gt; name, so you can block one and allow another.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;robots.txt&lt;/code&gt; controls crawling. It does &lt;strong&gt;not&lt;/strong&gt; hide pages or protect private data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocking &lt;code&gt;OAI-SearchBot&lt;/code&gt; removes you from ChatGPT search answers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who should read this:&lt;/strong&gt; site owners, bloggers and developers. &lt;strong&gt;Who can skip:&lt;/strong&gt; sites that already have a bot policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How AI Crawlers Work (and Why robots.txt Matters)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AI bots and web crawlers are automated programs that fetch your pages. Some collect text for LLM training data. Others fetch pages so that an AI search tool can display your link in a result.&lt;/p&gt;

&lt;p&gt;This is the crawling that you already know from Googlebot, with a difference: the purpose varies depending on which bot. Hence, AI crawler management requires more than one rule.&lt;/p&gt;

&lt;p&gt;Your robots.txt file is at the root of your site and uses robots.txt instructions such as User-agent, Allow and Disallow to indicate to each bot which resources it may fetch.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Should You Block AI Crawlers?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Block them only if you have a clear reason. There are three common ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You don't want your writing used for model training.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bots cause server load or &lt;strong&gt;bot traffic&lt;/strong&gt; you can't afford.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your content is paid or licensed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blocking everything has a cost. If you want &lt;strong&gt;AI search visibility&lt;/strong&gt;, such as getting cited in ChatGPT, you need to let the search crawlers in. Decide per bot, not per category of "AI."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;GPTBot vs OAI-SearchBot: Can You Separate Training from Search?&lt;/strong&gt;
&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%2Fnycx7elcf4vzpua4cz9h.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%2Fnycx7elcf4vzpua4cz9h.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes. OpenAI says each setting is independent. According to OpenAI's docs, &lt;code&gt;OAI-SearchBot&lt;/code&gt; surfaces sites in ChatGPT search, and sites that opt out will not appear in ChatGPT search answers.&lt;/p&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;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Company&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Job&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Block it if...&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPTBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Training&lt;/td&gt;
&lt;td&gt;You don't want training use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OAI-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Search results&lt;/td&gt;
&lt;td&gt;You don't want ChatGPT search visibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ChatGPT-User&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;User-triggered fetch&lt;/td&gt;
&lt;td&gt;OpenAI notes robots.txt may not apply here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClaudeBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Training&lt;/td&gt;
&lt;td&gt;You don't want training use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Claude-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;You don't want Claude search visibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Claude-User&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;User-triggered fetch&lt;/td&gt;
&lt;td&gt;You don't want user-requested fetches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Google-Extended&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Gemini AI use&lt;/td&gt;
&lt;td&gt;You want to opt out of Gemini use&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So yes, you can block &lt;code&gt;GPTBot&lt;/code&gt; and still allow ChatGPT search.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Google-Extended robots.txt: What It Does&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Google-Extended is a robots.txt token, not a separate crawler. Google documented it as a control over whether your content contributes to training of Gemini models and grounding.&lt;/p&gt;

&lt;p&gt;It has no effect on your Google Search ranking or indexing. Blocking it will not make you invisible to Google Search.&lt;/p&gt;

&lt;p&gt;Google also says that robots.txt controls crawling, not indexing. To keep a page out of search results, use noindex (the page must be crawlable for Google to see the tag), or put it behind a login.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;ClaudeBot robots.txt: The Anthropic Example&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Anthropic uses three user agents. &lt;code&gt;ClaudeBot&lt;/code&gt; collects content that may be used for training. &lt;code&gt;Claude-SearchBot&lt;/code&gt; improves search result quality. &lt;code&gt;Claude-User&lt;/code&gt; fetches pages when a person asks Claude about them.&lt;/p&gt;

&lt;p&gt;This split lets you opt out of training and stay visible in search, the same pattern as OpenAI.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Which AI Crawlers Should You Allow?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Allow AI search crawlers if you want traffic and citations. Most blogs and business sites gain from this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Allow:&lt;/strong&gt; &lt;code&gt;OAI-SearchBot&lt;/code&gt;, &lt;code&gt;Claude-SearchBot&lt;/code&gt;, &lt;code&gt;PerplexityBot&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usually block:&lt;/strong&gt; &lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, &lt;code&gt;CCBot&lt;/code&gt;, &lt;code&gt;Google-Extended&lt;/code&gt; (if you oppose training use)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leave to the platform:&lt;/strong&gt; &lt;code&gt;ChatGPT-User&lt;/code&gt;, &lt;code&gt;Claude-User&lt;/code&gt; (user-triggered)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to &lt;strong&gt;optimize content for AI search&lt;/strong&gt;, also keep pages &lt;a href="https://www.codewithamrendra.in/resources/blog/why-react-apps-slow-down-in-2026" rel="noopener noreferrer"&gt;fast&lt;/a&gt;, &lt;a href="https://www.codewithamrendra.in/services/web-development" rel="noopener noreferrer"&gt;server-rendered&lt;/a&gt;, and easy to read. Bots that can't see your text can't cite it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to Block AI Bots Using robots.txt&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Add this to &lt;code&gt;https://yourdomain.com/robots.txt&lt;/code&gt;. It blocks training bots and allows search bots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;
\&lt;span class="c"&gt;# Block AI training crawlers
&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;GPTBot&lt;/span&gt;

&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;ClaudeBot&lt;/span&gt;

&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;CCBot&lt;/span&gt;

&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;Google&lt;/span&gt;-&lt;span class="n"&gt;Extended&lt;/span&gt;

&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

\&lt;span class="c"&gt;# Allow AI search crawlers
&lt;/span&gt;
&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;OAI&lt;/span&gt;-&lt;span class="n"&gt;SearchBot&lt;/span&gt;

&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;Claude&lt;/span&gt;-&lt;span class="n"&gt;SearchBot&lt;/span&gt;

&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;PerplexityBot&lt;/span&gt;

&lt;span class="n"&gt;Allow&lt;/span&gt;: /

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

&lt;/div&gt;



&lt;p&gt;# Everyone else (including Googlebot)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;-agent: \*

&lt;span class="nc"&gt;Allow&lt;/span&gt;: /

Sitemap: https\://yourdomain.com/sitemap.xml

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

&lt;/div&gt;



&lt;p&gt;To block &lt;strong&gt;every&lt;/strong&gt; AI bot, switch the search bots to &lt;code&gt;Disallow: /&lt;/code&gt;. OpenAI says search changes can take about 24 hours to apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Check That It Works&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open &lt;code&gt;/robots.txt&lt;/code&gt; in your browser and confirm the rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check &lt;strong&gt;server logs&lt;/strong&gt; for bot names like &lt;code&gt;GPTBot&lt;/code&gt; and &lt;code&gt;OAI-SearchBot&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify real bots using the IP lists the vendors publish (for example, OpenAI's &lt;code&gt;gptbot.json&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you use Cloudflare or another CDN, check that your firewall isn't blocking bots you allowed. &lt;strong&gt;CDN and firewall bot blocking&lt;/strong&gt; often overrides robots.txt.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Common Mistakes to Avoid&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treating robots.txt as security.&lt;/strong&gt; It is a polite request. Bad bots can ignore it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A blanket&lt;/strong&gt; &lt;code&gt;Disallow: /&lt;/code&gt; &lt;strong&gt;under&lt;/strong&gt; &lt;code&gt;User-agent: *&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; This can block Googlebot too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blocking search bots by accident&lt;/strong&gt; and then wondering why AI tools never cite you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using robots.txt instead of&lt;/strong&gt; &lt;code&gt;noindex&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; A blocked page can still appear in results if other sites link to it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never checking logs.&lt;/strong&gt; You can't manage what you don't measure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For related reading on how Google treats AI-made content, see my post on &lt;a href="https://www.codewithamrendra.in/resources/blog/google-september-2026-spam-update-check-impact" rel="noopener noreferrer"&gt;Google's September 2026 spam update&lt;/a&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  &lt;strong&gt;Does robots.txt affect ChatGPT search?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yes. Allowing or blocking &lt;code&gt;OAI-SearchBot&lt;/code&gt; decides whether your site can appear in ChatGPT search answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Can I block GPTBot but allow ChatGPT search?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yes. Disallow &lt;code&gt;GPTBot&lt;/code&gt; and allow &lt;code&gt;OAI-SearchBot&lt;/code&gt;. They are independent settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Will blocking Google-Extended hurt my Google rankings?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;No. Google says it doesn't affect Search inclusion or ranking.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How do I control AI crawlers on my website?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Use one &lt;code&gt;User-agent&lt;/code&gt; block per bot in &lt;code&gt;robots.txt&lt;/code&gt;, then confirm with server logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Does robots.txt hide my page from Google?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;No. It controls crawling only. Use &lt;code&gt;noindex&lt;/code&gt; or authentication to keep pages out of results.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How do I get cited by ChatGPT?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Allow &lt;code&gt;OAI-SearchBot&lt;/code&gt;, publish clear and accurate content, and make sure pages load without heavy JavaScript.&lt;/p&gt;

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

&lt;p&gt;You don't have to make the same decision for every AI crawler. If you don't want your website content used for AI model training, consider blocking the relevant training crawlers. If you want your content to be discoverable in AI-powered search results, allow the search crawlers you want to support.&lt;/p&gt;

&lt;p&gt;Start by reviewing your website's &lt;code&gt;robots.txt&lt;/code&gt; file and checking whether its rules match your SEO goals. Test your configuration before publishing, and remember that &lt;code&gt;robots.txt&lt;/code&gt; is a crawler directive, not a security mechanism.&lt;/p&gt;

&lt;p&gt;Need help improving your website's technical SEO or AI search visibility?&lt;a href="https://codewithamrendra.in/contact" rel="noopener noreferrer"&gt; Contact me&lt;/a&gt; or explore my&lt;a href="https://codewithamrendra.in/services/seo-content-strategy" rel="noopener noreferrer"&gt; SEO and Content Strategy services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>seo</category>
    </item>
    <item>
      <title>GPT-6 Sol vs Claude Opus 5.5: Which Is Actually Cheaper for Agentic Workflows?</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:27:34 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/gpt-6-sol-vs-claude-opus-55-which-is-actually-cheaper-for-agentic-workflows-3ci3</link>
      <guid>https://dev.to/codewithamrendra/gpt-6-sol-vs-claude-opus-55-which-is-actually-cheaper-for-agentic-workflows-3ci3</guid>
      <description>&lt;p&gt;Everyone quotes the same two numbers: GPT-6 Sol is $2/$10 per million tokens, Claude Opus 5.5 is $4/$20. Sol looks half the price. Case closed, right?&lt;/p&gt;

&lt;p&gt;Not quite. An &lt;a href="https://www.codewithamrendra.in/category/ai-agents" rel="noopener noreferrer"&gt;AI coding agent&lt;/a&gt; reads a file, calls a tool, edits code, and calls another tool sometimes 20 or 50 times for one task. The sticker price barely matters once you factor in cache hits, retries, and how many tokens each model burns to finish. That's the number this post works out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Answer
&lt;/h2&gt;

&lt;p&gt;GPT-6 Sol is cheaper per token, but usually not per finished task. At list price, Sol is about $2/$10 per million input/output tokens versus Opus 5.5's $4/$20, making it roughly half the price.&lt;/p&gt;

&lt;p&gt;However, in a simulated 20-turn coding agent loop, Sol's cost savings drop from 1.87x to about 1.4-1.5x when accounting for the extra retries a cheaper model needs on hard tasks.&lt;/p&gt;

&lt;p&gt;For short, high-volume agent loops, Sol wins easily. But for longer, more difficult tasks where a single attempt matters, the actual price difference is much smaller than headline rates suggest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Price on paper: GPT-6 Sol vs Claude Opus 5.5
&lt;/h2&gt;

&lt;p&gt;Here's the sticker price for both, side by side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cost per 1 million tokens&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GPT-6 Sol&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Claude Opus 5.5&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$4.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cached input (repeat context)&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;td&gt;$20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context window&lt;/td&gt;
&lt;td&gt;~1.05M tokens&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max output&lt;/td&gt;
&lt;td&gt;128K tokens&lt;/td&gt;
&lt;td&gt;128K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two key points explain this difference. First, cached input costs the exact same rate ($0.20 per million tokens) across both models. Cached input refers to the part of your prompt the model has already processed, such as your codebase or chat history, which is billed at a discount. In long agent sessions, most tokens are cached repeats, making the initial input price gap far less significant.&lt;/p&gt;

&lt;p&gt;Second, past roughly 272K input tokens, Sol's rates increase for both input and output. When coding agents work on real software projects, they quickly cross this limit, steadily shrinking Sol's cost advantage over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real question: cost per finished task
&lt;/h2&gt;

&lt;p&gt;Token prices show what one request costs, but they do not tell you how many tokens a model needs to finish a job, how many retries it takes, or how much context gets cached.&lt;/p&gt;

&lt;p&gt;A cheaper model per token can end up costing more per finished task if it needs multiple attempts or writes longer responses. This cost per completed task is what actually determines your final bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: a 20-turn coding agent loop
&lt;/h2&gt;

&lt;p&gt;Let's say a coding agent needs 20 turns to complete a task, similar to the kind of multi-step automation covered in our piece on &lt;a href="https://www.codewithamrendra.in/resources/blog/ai-agents-replacing-saas-seats" rel="noopener noreferrer"&gt;AI agents replacing SaaS seats&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;During those turns, it might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read files&lt;/li&gt;
&lt;li&gt;Write or update code&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;li&gt;Check errors&lt;/li&gt;
&lt;li&gt;Fix problems&lt;/li&gt;
&lt;li&gt;Repeat until the task is complete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On average, each turn uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20,000 cached input tokens: existing conversation and project context&lt;/li&gt;
&lt;li&gt;3,000 fresh input tokens: new tool results, errors, or instructions&lt;/li&gt;
&lt;li&gt;2,000 output tokens: the model's response and generated code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost per Turn&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Token Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GPT-6 Sol&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Claude Opus 5.5&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cached input (20K)&lt;/td&gt;
&lt;td&gt;$0.004&lt;/td&gt;
&lt;td&gt;$0.004&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fresh input (3K)&lt;/td&gt;
&lt;td&gt;$0.006&lt;/td&gt;
&lt;td&gt;$0.012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output (2K)&lt;/td&gt;
&lt;td&gt;$0.020&lt;/td&gt;
&lt;td&gt;$0.040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total per turn&lt;/td&gt;
&lt;td&gt;$0.030&lt;/td&gt;
&lt;td&gt;$0.056&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the agent needs 20 turns to finish the task:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Cost for 20 Turns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-6 Sol&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 5.5&lt;/td&gt;
&lt;td&gt;$1.12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So, in this example, Sol costs about 1.87x less than Opus 5.5.&lt;/p&gt;

&lt;h3&gt;
  
  
  But There Is Another Cost: Retries
&lt;/h3&gt;

&lt;p&gt;A cheaper model is not always cheaper if it needs more attempts to complete the task successfully.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Sol needs 1.4 attempts on average&lt;/li&gt;
&lt;li&gt;Opus 5.5 needs 1.1 attempts on average&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the estimated cost per successfully completed task becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sol: $0.60 x 1.4 = $0.84&lt;/li&gt;
&lt;li&gt;Opus 5.5: $1.12 x 1.1 = $1.23&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That changes the difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before retries: 1.87x cheaper&lt;/li&gt;
&lt;li&gt;After retries: about 1.47x cheaper&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The cheapest model on paper may not always be the cheapest model in real-world use.&lt;/p&gt;

&lt;p&gt;When comparing coding agents, track these three numbers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tokens used per turn&lt;/li&gt;
&lt;li&gt;Number of turns per task&lt;/li&gt;
&lt;li&gt;How often the agent needs to retry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then use those numbers to calculate your real cost per completed task.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The retry rates above are only examples, not published benchmark results. Your actual results may be different depending on the type and difficulty of your coding tasks.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden costs and mistakes to avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hidden Costs That Can Increase Your AI Bill
&lt;/h3&gt;

&lt;p&gt;The price shown in a model's pricing table is only part of the story. A few other factors can increase your actual costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Runaway Reasoning Costs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some models can spend a large amount of tokens on reasoning before producing an answer.&lt;/p&gt;

&lt;p&gt;For example, testing has shown that Opus 5.5 at its highest "max" reasoning setting can use its full 128K output limit without producing a usable answer. You can still be charged for that failed request.&lt;/p&gt;

&lt;p&gt;This becomes more expensive when an agent automatically retries the task. Setting a reasonable limit on reasoning effort can help control this cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Long-Context Costs on Sol&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sol becomes more expensive when the input context gets very large.&lt;/p&gt;

&lt;p&gt;Once the input goes beyond roughly 272K tokens, both input and output pricing increases.&lt;/p&gt;

&lt;p&gt;This matters for coding agents working with large codebases because they can reach this limit faster than expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Fast and Batch Modes Have Trade-Offs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Faster processing usually costs more.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sol Fast: roughly 2x the normal rate&lt;/li&gt;
&lt;li&gt;Opus 5.5 Fast: $8/$40, with speeds of up to around 2.5x&lt;/li&gt;
&lt;li&gt;Sol Batch: roughly 50% cheaper, but it is designed for background processing rather than real-time tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fast mode makes sense when speed is essential, whereas Batch mode is better suited for background jobs that do not need immediate answers, an architecture decision that often overlaps with how teams handle their &lt;a href="https://www.codewithamrendra.in/services/cloud-devops" rel="noopener noreferrer"&gt;cloud and DevOps setup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Don't Compare Models by Token Price Alone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the biggest mistakes is looking only at the price per token and using that to estimate your monthly bill.&lt;/p&gt;

&lt;p&gt;Your real cost also depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many turns an agent needs&lt;/li&gt;
&lt;li&gt;How much output it generates&lt;/li&gt;
&lt;li&gt;How often it retries&lt;/li&gt;
&lt;li&gt;How much context it sends&lt;/li&gt;
&lt;li&gt;Whether you use Fast or Batch processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Bottom Line
&lt;/h3&gt;

&lt;p&gt;If you're building AI agents or automation for a product, calculate the expected cost using your actual workloads before choosing a model at scale.&lt;/p&gt;

&lt;p&gt;Looking at token prices alone can give you a very different number from your real monthly bill.&lt;/p&gt;

&lt;p&gt;This is also an area where our &lt;a href="https://www.codewithamrendra.in/services/ai-automation" rel="noopener noreferrer"&gt;AI automation work&lt;/a&gt; can help teams model and control their AI costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Model Should You Pick?
&lt;/h2&gt;

&lt;p&gt;There isn't one model that is best for every coding task. The right choice depends on the type of work, how often you run it, and how expensive retries are.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Your situation&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;What to consider first&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Why&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High-volume, repetitive coding tasks&lt;/td&gt;
&lt;td&gt;GPT-6 Sol&lt;/td&gt;
&lt;td&gt;Lower token costs can make it more economical for frequent tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Difficult tasks where retries are expensive&lt;/td&gt;
&lt;td&gt;Claude Opus 5.5&lt;/td&gt;
&lt;td&gt;A higher success rate can sometimes justify the higher cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long agent sessions with lots of context&lt;/td&gt;
&lt;td&gt;Calculate the cost per session&lt;/td&gt;
&lt;td&gt;Sol's higher pricing after 272K input tokens can change the cost comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple classification or routing&lt;/td&gt;
&lt;td&gt;Use a smaller model&lt;/td&gt;
&lt;td&gt;Sol and Opus 5.5 may be more capable than the task requires&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  A Practical Approach
&lt;/h3&gt;

&lt;p&gt;Instead of using the same model for every task, you can use a tiered approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with GPT-6 Sol for regular coding tasks.&lt;/li&gt;
&lt;li&gt;Monitor failures, retries, and token usage.&lt;/li&gt;
&lt;li&gt;Send difficult or high-risk tasks to Claude Opus 5.5 when the extra reliability is worth the additional cost.&lt;/li&gt;
&lt;li&gt;Use smaller, cheaper models for simple tasks such as classification, routing, or basic extraction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach lets you balance cost, speed, and reliability instead of optimizing for token price alone.&lt;/p&gt;

&lt;p&gt;If you're designing an AI agent pipeline, our AI agents content and &lt;a href="https://www.codewithamrendra.in/resources/case-studies" rel="noopener noreferrer"&gt;case studies&lt;/a&gt; go deeper into the architecture and implementation side.&lt;/p&gt;

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

&lt;p&gt;GPT-6 Sol appears much cheaper than Claude Opus 5.5 when comparing only headline token prices. However, real-world agent costs depend on much more than list rates. Retries, context size, cache usage, reasoning effort, and the number of turns all impact the final bill.&lt;/p&gt;

&lt;p&gt;Sol is a practical choice for high-volume, repetitive coding tasks, while Opus 5.5 works well for complex tasks where failed attempts are expensive. The key is to compare total cost per completed task rather than price per token. Test both models on your specific workload, track retries and token usage, and use those real metrics to guide your choice.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AWS vs Google Cloud for Startups: Which Cloud Platform Should You Choose in 2026?</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Thu, 03 Sep 2026 11:15:14 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/aws-vs-google-cloud-for-startups-which-cloud-platform-should-you-choose-in-2026-53h2</link>
      <guid>https://dev.to/codewithamrendra/aws-vs-google-cloud-for-startups-which-cloud-platform-should-you-choose-in-2026-53h2</guid>
      <description>&lt;p&gt;Picking a cloud provider on day one of a startup feels bigger than it is. Both AWS and Google Cloud can run almost any workload you throw at them. The real decision comes down to your team's skills, your budget, and how much hand-holding you want from the platform. &lt;/p&gt;

&lt;p&gt;Whatever you decide, it's one piece of a bigger technical picture, and it helps to see the &lt;a href="https://www.codewithamrendra.in/services" rel="noopener noreferrer"&gt;full range of services&lt;/a&gt; a startup typically needs before locking in any single choice.&lt;/p&gt;

&lt;p&gt;This guide breaks down the cloud computing patterns worth knowing, compares real 2026 pricing, clears up what Docker actually is (yes, people really search "Docker urban dictionary"), and walks through the Well-Architected Framework so you don't over-engineer your first product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Answer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick AWS&lt;/strong&gt; if you want the widest service catalog, more third-party tutorials, and up to $100,000 in AWS Activate credits. &lt;strong&gt;Pick Google Cloud&lt;/strong&gt; if your product leans on AI/ML workloads, since Google for Startups offers up to $200,000 in credits for AI-focused companies, and its per-pod GKE pricing keeps container costs predictable.&lt;/p&gt;

&lt;p&gt;Most early-stage teams outgrow free credits in 6-12 months either way, so architecture discipline matters more than the logo on your invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Computing Patterns You Need Before Choosing
&lt;/h2&gt;

&lt;p&gt;Before comparing vendors, get the patterns straight, since they matter more than the provider logo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Microservices vs. modular monolith&lt;/strong&gt;: split your app into small services only once a real team-scaling problem shows up. Before that, a modular monolith ships faster with fewer moving parts. If you're weighing this trade-off in detail, we broke it down fully in &lt;a href="https://www.codewithamrendra.in/resources/blog/microservices-vs-modular-monolith-2026" rel="noopener noreferrer"&gt;microservices vs. modular monolith in 2026&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event-driven architecture&lt;/strong&gt;: services talk through queues or event buses (SQS/EventBridge on AWS, Pub/Sub on Google Cloud) instead of calling each other directly. This keeps one slow service from taking down the rest.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-scaling / elastic compute&lt;/strong&gt;: pay for capacity only when traffic needs it. Both clouds do this well; the difference is how much configuration it takes to get there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Managed database over self-hosted&lt;/strong&gt;: startups rarely have the headcount to patch and back up their own database servers. RDS/Aurora and Cloud SQL/Spanner exist so you don't have to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt;: Terraform or each provider's native tool (CloudFormation, Deployment Manager) so your infrastructure is versioned, not clicked together in a console.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These patterns are cloud-agnostic. Get comfortable with them first, and switching providers later becomes a config change, not a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS vs Google Cloud: Pricing for Startups in 2026
&lt;/h2&gt;

&lt;p&gt;AWS Activate gives startups up to $100,000 in credits, while Google for Startups offers up to $200,000 for AI-focused companies, but credits run out, and what you pay after matters more.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;AWS&lt;/th&gt;
&lt;th&gt;Google Cloud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Startup credits&lt;/td&gt;
&lt;td&gt;Up to $100K (AWS Activate)&lt;/td&gt;
&lt;td&gt;Up to $200K for AI startups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute free tier&lt;/td&gt;
&lt;td&gt;750 hrs/month for 12 months&lt;/td&gt;
&lt;td&gt;1 f1-micro instance, free indefinitely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container pricing&lt;/td&gt;
&lt;td&gt;EKS bills per node you manage&lt;/td&gt;
&lt;td&gt;GKE Autopilot charges per pod and manages the control plane and nodes for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egress pricing&lt;/td&gt;
&lt;td&gt;~$0.09/GB after free tier&lt;/td&gt;
&lt;td&gt;~$0.08-0.12/GB, tiered by volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Broadest service catalog, hiring pool&lt;/td&gt;
&lt;td&gt;AI/ML workloads, simpler container ops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a small team running containers, GKE Autopilot cuts operational overhead noticeably compared to manually managing EKS node groups. On the other hand, AWS's sheer size means almost every problem already has a Stack Overflow answer and a hire who's used it before, which counts for something when you're a five-person team without a dedicated DevOps hire.&lt;/p&gt;

&lt;p&gt;One real trap: cross-region data transfer. Put your app server in one region and your database in another, and every request pays an inter-region egress fee on both clouds. Teams have cut cloud bills by more than half just by colocating resources in the same region, so check this before you optimize anything else.&lt;/p&gt;

&lt;p&gt;If your team doesn't have in-house DevOps expertise yet, that's usually the actual bottleneck, not the provider. Our &lt;a href="https://www.codewithamrendra.in/services/cloud-devops" rel="noopener noreferrer"&gt;cloud &amp;amp; DevOps team&lt;/a&gt; sets up cost-safe infrastructure from the start so you're not relearning this the hard way at $12K/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Docker Actually Means
&lt;/h2&gt;

&lt;p&gt;If you searched "Docker urban dictionary," you're probably half-joking and half-confused why developers say "just Dockerize it" like it's obvious. Here's the real, boring-but-useful definition:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; is a tool that packages your app plus everything it needs to run, code, runtime, libraries, settings, into a single unit called a &lt;strong&gt;container&lt;/strong&gt;. That container runs the same way on your laptop, your teammate's laptop, and in production. No more "it works on my machine."&lt;/p&gt;

&lt;p&gt;It's not a cloud provider, not a programming language, and not related to actual shipping docks, though the container metaphor is intentional. &lt;/p&gt;

&lt;p&gt;If the underlying JavaScript and runtime concepts inside that container still feel fuzzy, this walkthrough of &lt;a href="https://www.codewithamrendra.in/resources/blog/javascript-enlightenment-master-core-concepts" rel="noopener noreferrer"&gt;core JavaScript concepts&lt;/a&gt; fills in a lot of the gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Use Cases for Early-Stage Startups
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistent local development&lt;/strong&gt;: every new hire runs &lt;code&gt;docker compose up&lt;/code&gt; and gets the exact same environment, no manual setup docs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD pipelines&lt;/strong&gt;: build once, test in a container, deploy that same container to staging and production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Microservices packaging&lt;/strong&gt;: each service ships in its own container with its own dependencies, so upgrading one doesn't break another. This is exactly the pattern behind something like a &lt;a href="https://www.codewithamrendra.in/resources/blog/rag-chatbot-for-your-business-website" rel="noopener noreferrer"&gt;RAG chatbot for your business website&lt;/a&gt;, where the retrieval service usually runs as its own container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Running on either cloud without lock-in&lt;/strong&gt;: a containerized app moves between AWS ECS/EKS and Google Cloud Run/GKE with minimal rework, which keeps your options open if pricing shifts later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local testing of managed services&lt;/strong&gt;: tools like LocalStack let you mimic AWS services in a container before touching real infrastructure and racking up charges.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same container-first thinking is why more of the manual ops work founders used to do by hand is increasingly handled by autonomous workflows; we cover that shift in &lt;a href="https://www.codewithamrendra.in/resources/blog/ai-agents-replacing-saas-seats" rel="noopener noreferrer"&gt;why AI agents are replacing SaaS seats in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Architecture Diagram: A Startup Stack on Each Cloud
&lt;/h2&gt;

&lt;p&gt;A typical early-stage SaaS architecture looks almost identical on both clouds, only the service names change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → CDN → Load Balancer → App Servers (containers) → Cache → Database
                                     ↓
                              Object Storage (uploads, assets)
                                     ↓
                              Queue → Background Workers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS version:&lt;/strong&gt; CloudFront → ALB → ECS/EKS → ElastiCache → RDS/Aurora, with S3 for storage and SQS for queues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Cloud version:&lt;/strong&gt; Cloud CDN → Cloud Load Balancing → Cloud Run/GKE → Memorystore → Cloud SQL/Spanner, with Cloud Storage and Pub/Sub.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the shape doesn't change; this is the point of learning patterns before providers. If your team is still deciding how to structure this for a real product, our &lt;a href="https://www.codewithamrendra.in/services/web-development" rel="noopener noreferrer"&gt;web development team&lt;/a&gt; has built this exact stack for founders who'd rather ship features than debug VPC configs.&lt;/p&gt;

&lt;p&gt;The backend shape barely shifts between providers, but the frontend layer sitting on top of it keeps evolving fast; this look at the &lt;a href="https://www.codewithamrendra.in/resources/blog/future-of-web-development-2026" rel="noopener noreferrer"&gt;future of web development in 2026&lt;/a&gt; covers where that's headed. &lt;/p&gt;

&lt;p&gt;Even small UI details, like the one walked through in &lt;a href="https://www.codewithamrendra.in/resources/blog/how-to-build-a-light-and-dark-theme-switch-using-javascript" rel="noopener noreferrer"&gt;building a light and dark theme switch with JavaScript&lt;/a&gt;, stay completely unaffected by which cloud sits underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Well-Architected Framework: AWS vs Google Cloud
&lt;/h2&gt;

&lt;p&gt;AWS's Well-Architected Framework provides best practices across six pillars: operational excellence, security, reliability, performance efficiency, cost optimization, and sustainability (added later to account for the environmental cost of running workloads).&lt;/p&gt;

&lt;p&gt;Google Cloud has its own equivalent, the Architecture Framework, built around nearly identical pillars: operational excellence, security/privacy/compliance, reliability, cost optimization, and performance optimization.&lt;/p&gt;

&lt;p&gt;For a startup, you don't need to formally review against all six pillars on day one. Focus on three early:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: lock down IAM roles and never hardcode credentials, from commit one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost optimization&lt;/strong&gt;: set billing alerts before you set up your first database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: know what happens when your one server goes down, even if the answer today is "we get paged and restart it manually."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Revisit the full framework once you have paying customers and something real to lose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Costs, Mistakes, and Red Flags
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idle resources&lt;/strong&gt;: a forgotten staging database or an orphaned load balancer bills you every month until someone notices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-region traffic&lt;/strong&gt;: as noted above, this quietly inflates bills more than compute usually does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-provisioned databases&lt;/strong&gt;: teams default to enterprise-tier databases during free-credit periods, then can't justify downgrading later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No budget alerts&lt;/strong&gt;: the single easiest fix, and the one most skipped-over setting in both consoles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choosing based on credits alone&lt;/strong&gt;: credits expire in months; your architecture and monthly burn rate outlive them by years.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud infrastructure is only one line item in a startup budget. If you're also scoping the cost of the product itself, this breakdown of &lt;a href="https://www.codewithamrendra.in/resources/blog/how-much-does-a-nextjs-website-cost-in-2026" rel="noopener noreferrer"&gt;what a Next.js website costs in 2026&lt;/a&gt; is a useful companion to the numbers above.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Is Google Cloud cheaper than AWS for startups?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It depends on your workload. GCP tends to be cheaper for container-heavy and AI workloads due to per-pod billing and sustained-use discounts; AWS is often cheaper for simple compute and storage due to its wider range of instance sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which cloud is easier to learn for a first-time founder?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Cloud's console and pricing model are generally considered simpler. AWS has a steeper learning curve but far more tutorials, courses, and hires who already know it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I switch clouds later if I start with the wrong one?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, especially if you containerize with Docker from the start and use Infrastructure as Code. It's real work, but not a rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need Kubernetes as a small startup?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually not on day one. A single container on Cloud Run, ECS Fargate, or even a plain VM is enough until you have multiple services that need independent scaling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the AWS Well-Architected Framework used for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a structured way to review your cloud architecture against best practices in security, cost, reliability, performance, operations, and sustainability, useful once you have a real product, overkill for a prototype.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much do startup cloud credits actually cover?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enough for 6-12 months of moderate usage in most cases. Google Cloud offers the highest maximum credits at $200,000 over two years for qualifying startups, but plan your post-credit budget at least six months before they run out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I pick a cloud based on AI/ML needs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If AI is core to your product, yes: Google's TPU access provides notably better performance per dollar for specific machine learning workloads. If AI is a feature, not the product, either cloud's GPU instances work fine. If you need help scoping AI infrastructure, our &lt;a href="https://www.codewithamrendra.in/services/ai-automation" rel="noopener noreferrer"&gt;AI &amp;amp; automation team&lt;/a&gt; can review what your product actually needs before you commit spend.&lt;/p&gt;

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

&lt;p&gt;There's no universally "better" cloud in 2026. AWS and Google Cloud both handle real production workloads without breaking a sweat. AWS wins on breadth, hiring pool, and startup credit size. Google Cloud wins on simpler container pricing, an indefinite free tier, and better economics if AI/ML is core to your product.&lt;/p&gt;

&lt;p&gt;What actually decides the outcome for most startups isn't the provider, it's whether the architecture follows sound patterns from day one: containerize with Docker, keep resources in one region, set budget alerts before you set up a database, and use Infrastructure as Code so switching later stays possible.&lt;/p&gt;

&lt;p&gt;Getting infrastructure spend under control early also frees up budget for the parts of the business that actually bring in users.&lt;/p&gt;

&lt;p&gt;That's usually &lt;a href="https://www.codewithamrendra.in/services/digital-marketing" rel="noopener noreferrer"&gt;digital marketing&lt;/a&gt; once you have a working product, paired with an &lt;a href="https://www.codewithamrendra.in/services/seo-content-strategy" rel="noopener noreferrer"&gt;SEO and content strategy&lt;/a&gt; to make sure people can find it.&lt;/p&gt;

&lt;p&gt;If you're still unsure which way to go, start smaller than you think you need to, watch your bill weekly for the first three months, and revisit the Well-Architected (or Google's Architecture Framework) pillars once you have real users and something worth protecting. &lt;/p&gt;

&lt;p&gt;For more guides like this one, our &lt;a href="https://www.codewithamrendra.in/resources/blog" rel="noopener noreferrer"&gt;blog&lt;/a&gt; covers the rest of the stack too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Written by Amrendra&lt;/strong&gt;: Frontend Developer &amp;amp; Technical Content Writer, working hands-on with React and Next.js projects, and helping startups make cloud and architecture decisions that don't blow up their runway.&lt;/p&gt;

&lt;p&gt;If you're newer to the ecosystem, &lt;a href="https://www.codewithamrendra.in/resources/blog/how-to-learn-react" rel="noopener noreferrer"&gt;how to learn React&lt;/a&gt; is a good place to start.&lt;/p&gt;

&lt;p&gt;Need help picking and setting up the right cloud stack, or the &lt;a href="https://www.codewithamrendra.in/services/ui-ux-product-design" rel="noopener noreferrer"&gt;UI/UX and product design&lt;/a&gt; layer that sits on top of it? &lt;a href="https://www.codewithamrendra.in/contact" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Get System Theme in JavaScript: Detect Dark/Light Mode Automatically</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Wed, 19 Aug 2026 07:23:14 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/get-system-theme-in-javascript-detect-darklight-mode-automatically-pbo</link>
      <guid>https://dev.to/codewithamrendra/get-system-theme-in-javascript-detect-darklight-mode-automatically-pbo</guid>
      <description>&lt;p&gt;Last month I was fixing a theme bug on a client project under our &lt;a href="https://www.codewithamrendra.in/services/web-development" rel="noopener noreferrer"&gt;web development services&lt;/a&gt;, and the issue was frustrating. The site looked perfectly fine on my laptop, but a user on macOS with dark mode enabled complained about a blinding white flash every time the page loaded.&lt;/p&gt;

&lt;p&gt;That single ticket taught me more about system theme detection than any tutorial ever did, and it is the reason I am writing this guide the way I wish someone had explained it to me back then.&lt;/p&gt;

&lt;p&gt;If you are building a website or web app in 2026, respecting a visitor's operating system preference is no longer optional. Browsers, phones, and desktops all let users choose between light and dark mode at the system level, and modern users expect websites to follow that choice automatically instead of forcing them to hunt for a toggle button.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does "System Theme" Actually Mean
&lt;/h2&gt;

&lt;p&gt;When someone switches their OS to dark mode, whether on Windows, macOS, Android, or iOS, that preference gets exposed to the browser through a CSS media feature called &lt;code&gt;prefers-color-scheme&lt;/code&gt;. JavaScript can read this same signal using the &lt;code&gt;window.matchMedia()&lt;/code&gt; API, which is built into every modern browser and needs no external library.&lt;/p&gt;

&lt;p&gt;In short, you are not guessing the user's preference. You are reading a value the operating system already reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Code Snippet
&lt;/h2&gt;

&lt;p&gt;Here is the simplest way to check the current system preference:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefersDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefersDark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single line does the heavy lifting. &lt;code&gt;matchMedia&lt;/code&gt; returns a &lt;code&gt;MediaQueryList&lt;/code&gt; object, and its &lt;code&gt;matches&lt;/code&gt; property tells you whether the query currently applies. When I first used this in production, I was surprised how little code was actually required compared to the workarounds I had seen in older projects that relied on cookies or manual detection scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reacting to Live Theme Changes
&lt;/h2&gt;

&lt;p&gt;A static check only tells you the preference at page load. Many users switch their system theme during the day, especially on laptops that auto-switch at sunset, so your site should react without needing a refresh. Add an event listener to the same media query object:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added this listener to a SaaS dashboard we shipped earlier this year, and support tickets about "the site looks wrong" dropped noticeably because the interface now matched whatever the user's OS was doing in real time, without a manual reload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding the Flash of Wrong Theme
&lt;/h2&gt;

&lt;p&gt;One mistake I made early on was placing this detection script at the bottom of the page, right before the closing body tag. That caused a visible flash of the wrong theme for a split second before JavaScript ran. The fix is simple: put a small inline script in the document head, before any stylesheet loads, so the theme attribute is set before the browser paints anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tiny change removed the flicker completely on every project where I applied it since.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining System Detection With a Manual Toggle
&lt;/h2&gt;

&lt;p&gt;Detecting the system theme is only half the picture. Most users still expect a toggle button so they can override the OS setting for that specific site. If you want the full implementation, including how to store the user's manual choice with localStorage so it persists across visits, we covered that in detail in our guide on &lt;a href="https://www.codewithamrendra.in/resources/blog/how-to-build-a-light-and-dark-theme-switch-using-javascript" rel="noopener noreferrer"&gt;building a light and dark theme switch using JavaScript&lt;/a&gt;. Read that alongside this article if you need both automatic detection and a manual override in the same project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Fits Into Product Design
&lt;/h2&gt;

&lt;p&gt;System theme detection is not just a coding exercise. It directly affects how polished a product feels to real users, which is why our &lt;a href="https://www.codewithamrendra.in/services/ui-ux-product-design" rel="noopener noreferrer"&gt;UI/UX and product design&lt;/a&gt; work always includes theme handling as a baseline requirement, not an afterthought. A product that respects a user's system preference from the first paint feels considerably more trustworthy than one that ignores it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;Based on the projects I have reviewed for clients, these are the errors that show up most often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking the preference only once and never listening for changes, so the theme goes stale if the user switches mid-session.&lt;/li&gt;
&lt;li&gt;Running the detection script after the CSS has already loaded, which causes the flash mentioned earlier.&lt;/li&gt;
&lt;li&gt;Forgetting to remove the event listener when a component unmounts in frameworks like React, which can cause memory leaks on single-page applications.&lt;/li&gt;
&lt;li&gt;Assuming &lt;code&gt;matchMedia&lt;/code&gt; is always supported. It is available in every current browser, but a quick &lt;code&gt;if (window.matchMedia)&lt;/code&gt; guard is still good defensive coding for older environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing Your Implementation Properly
&lt;/h2&gt;

&lt;p&gt;Do not rely only on changing your OS setting to test this feature, since that is slow and easy to forget. Chrome, Firefox, and Safari all let you emulate &lt;code&gt;prefers-color-scheme&lt;/code&gt; directly inside developer tools, which makes testing both states in seconds instead of minutes.&lt;/p&gt;

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

&lt;p&gt;Reading the system theme in JavaScript takes only a few lines of code, but getting it right, without flicker, without stale state, and with proper cleanup, is what separates a rushed implementation from a genuinely polished one. This is the exact kind of detail we handle across our &lt;a href="https://www.codewithamrendra.in/category/frontend-development" rel="noopener noreferrer"&gt;frontend development&lt;/a&gt; and &lt;a href="https://www.codewithamrendra.in/category/react" rel="noopener noreferrer"&gt;React&lt;/a&gt; work at Code with Amrendra.&lt;/p&gt;

&lt;p&gt;If you are working through a similar frontend challenge or want a second pair of eyes on your theming setup, browse more guides on our &lt;a href="https://www.codewithamrendra.in/resources/blog" rel="noopener noreferrer"&gt;engineering blog&lt;/a&gt; or reach out through our &lt;a href="https://www.codewithamrendra.in/contact" rel="noopener noreferrer"&gt;contact page&lt;/a&gt; and we will be glad to help.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Microservices vs Modular Monolith in 2026: Which Architecture Actually Scales?</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Wed, 05 Aug 2026 10:22:34 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/microservices-vs-modular-monolith-in-2026-which-architecture-actually-scales-1iie</link>
      <guid>https://dev.to/codewithamrendra/microservices-vs-modular-monolith-in-2026-which-architecture-actually-scales-1iie</guid>
      <description>&lt;p&gt;Choosing between a modular monolith and microservices is one of the most important architectural decisions for modern software teams. While microservices remain popular for large-scale distributed systems, they also introduce operational complexity that many applications do not need.&lt;/p&gt;

&lt;p&gt;In 2026, the industry trend is shifting toward modular monoliths as a practical starting point, with services extracted only when business and organizational requirements justify the transition.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This article gives you a code-level pattern for enforcing module boundaries, a comparison table with real cost figures, and a checklist to decide when extraction is justified. You will be able to structure a modular monolith that avoids the "big ball of mud" failure mode and know exactly which signals mean it is time to split a service out. For a deeper look at how these boundaries hold up at scale, see this breakdown of&lt;a href="https://codewithamrendra.vercel.app/category/saas-architecture" rel="noopener noreferrer"&gt; SaaS system design and multi-tenant architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What a Modular Monolith Actually Is&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A modular monolith is not "a monolith with folders." It requires enforced module boundaries at the code level, not convention-based ones that erode after six sprints.&lt;/p&gt;

&lt;p&gt;/src&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;/billing&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/internal&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;- not importable from other modules&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;api.ts&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;- the only public surface&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;/catalog&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/internal&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;api.ts&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;/shipping&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/internal&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;api.ts&lt;/p&gt;

&lt;p&gt;Each module exposes one &lt;code&gt;api.ts&lt;/code&gt; file. Nothing outside &lt;code&gt;/billing&lt;/code&gt; imports from &lt;code&gt;/billing/internal&lt;/code&gt;. Linting rules (ESLint boundaries plugin, or a build-time check in CI) fail the build if that rule is broken. This is the difference between a modular monolith and a monolith that will need a rewrite in two years.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Microservices vs Modular Monolith: Comparison Table&lt;/strong&gt;
&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;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Factor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Modular Monolith&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Microservices&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Development Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Faster&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Slower initially&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD Complexity&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Easier&lt;/td&gt;
&lt;td&gt;More complex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;Simple&lt;/td&gt;
&lt;td&gt;Requires observability tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Curve&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Extensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment Risk&lt;/td&gt;
&lt;td&gt;Single deployment&lt;/td&gt;
&lt;td&gt;Isolated deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: AWS microservices architecture documentation (aws.amazon.com/docs), byteiota 2026 cost analysis. Last updated: August 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When to Extract: The Signal Checklist&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Do not extract a service because a blog post told you to. Extract when at least two of these are true at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;One module needs 10x the compute or scaling profile of the rest of the system&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Two or more teams are blocked waiting on each other's deploys inside the same codebase&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A specific module has a regulatory isolation requirement, such as PCI-scoped payment processing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Engineering headcount on the codebase has passed roughly 50 and merge conflicts across domains are routine&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of these apply, extraction adds network latency and operational surface area for no measurable gain.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;From Startup to Platform Engineering: A Practical Architecture Evolution&lt;/strong&gt;&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;Most successful software products do not begin with microservices. They start as a &lt;strong&gt;modular monolith&lt;/strong&gt;, allowing teams to ship features quickly while keeping operational complexity low. As the product grows and more developers contribute, clear &lt;strong&gt;bounded contexts&lt;/strong&gt; help organize the codebase into independent business domains without splitting the application.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;When certain modules require independent scaling, faster release cycles, or regulatory isolation, those well-defined boundaries make it easier to extract &lt;strong&gt;selective microservices&lt;/strong&gt; with minimal disruption.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Over time, larger engineering organizations often adopt &lt;strong&gt;platform engineering&lt;/strong&gt; to standardize deployments, observability, security, and developer workflows. This evolutionary approach balances simplicity today with flexibility for tomorrow, avoiding unnecessary complexity while preparing the system for long-term growth.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Distributed Monolith Trap&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A distributed monolith happens when teams deploy separate services that still share a database or make synchronous chained calls to complete one request. You get all the network cost of microservices with none of the independence benefit. As per the GitHub-documented modular monolith reference implementations (github.com), the fix is establishing bounded contexts through Domain-Driven Design before any physical service split, not after. If your "microservices" cannot deploy independently without coordinating a release, you have not built microservices. You have built a monolith with extra network hops.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;1. What is a modular monolith?&lt;/strong&gt; A single deployable application organized into modules with enforced boundaries: separate internal namespaces, one public API per module, and no direct imports across module internals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. When should you choose microservices over a modular monolith?&lt;/strong&gt; When independent scaling, regulatory isolation, or a team size past roughly 50 engineers creates coordination costs that outweigh the network and operational overhead of distributed services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Can a modular monolith scale like microservices?&lt;/strong&gt; Yes for most workloads. Shopify runs a modular monolith at enterprise transaction volume by keeping strict internal boundaries and extracting only specific high-load services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How do you migrate from a modular monolith to microservices?&lt;/strong&gt; Identify bounded contexts using Domain-Driven Design, confirm the module already has a clean internal API, then extract it behind that same interface as a networked service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. What is a distributed monolith and why is it bad?&lt;/strong&gt; It is multiple deployed services that still share a database or require synchronous coordinated releases. It combines microservices' network cost with a monolith's coupling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Do you need Domain-Driven Design for a modular monolith?&lt;/strong&gt; Not strictly, but bounded context modeling is the most reliable method for drawing module boundaries that will not need to be redrawn after launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. How many developers before microservices make sense?&lt;/strong&gt; Most 2026 industry data points to roughly 50 to 100 engineers as the threshold where coordination overhead in a single codebase starts to exceed distributed systems overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Is Shopify a monolith or microservices?&lt;/strong&gt; Shopify's core platform is a modular monolith built on Ruby on Rails, with services extracted selectively only where independent scaling justifies it.&lt;/p&gt;

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

&lt;p&gt;Amrendra Kumar is a software engineer and technical writer at&lt;br&gt;
&lt;a href="https://codewithamrendra.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;/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>architecture</category>
      <category>microservices</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>AI Chatbots for Small Business: Do You Really Need One?</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Wed, 29 Jul 2026 09:54:43 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/ai-chatbots-for-small-business-do-you-really-need-one-4o9b</link>
      <guid>https://dev.to/codewithamrendra/ai-chatbots-for-small-business-do-you-really-need-one-4o9b</guid>
      <description>&lt;p&gt;&lt;strong&gt;AI chatbots for small business&lt;/strong&gt; make sense when repetitive questions eat more than a few hours a week, or when traffic arrives after hours with nobody there to answer it. code with Amrendra cost far less per interaction than a human agent and typically pay for themselves within one to three months.&lt;/p&gt;

&lt;p&gt;They are a poor fit for low-traffic sites or complex, high-touch sales conversations that need a real person on the other end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article answers one question directly: does your business actually need an AI chatbot, or is it another tool you will pay for and ignore by month three. Most content on this topic comes from chatbot vendors, so the numbers get generous fast.&lt;/p&gt;

&lt;p&gt;Here you get the real cost math, the signals that matter, and the cases where a chatbot is the wrong call. &lt;/p&gt;

&lt;p&gt;If you are also weighing broader automation beyond support tickets, our &lt;a href="https://codewithamrendra.vercel.app/category/ai-agents" rel="noopener noreferrer"&gt;AI agent workflows and automation guides&lt;/a&gt; cover the wider landscape. &lt;/p&gt;

&lt;p&gt;By the end, you will have a simple framework to decide, not a sales pitch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an AI Chatbot Actually Does for a Small Business
&lt;/h2&gt;

&lt;p&gt;An AI chatbot for small business handles three jobs well: answering repeated FAQs, capturing leads outside business hours, and routing complex questions to a human.&lt;/p&gt;

&lt;p&gt;It does not run your business for you. Most tools train on your existing website content or help docs and go live in under a day. The value is not the technology itself. It is the hours it takes off your plate every week that you were previously spending on the same five questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost-Per-Interaction Math
&lt;/h2&gt;

&lt;p&gt;Here is the number that actually matters before you buy anything: what does each customer interaction cost you today, and what would it cost through a bot.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Support method&lt;/th&gt;
&lt;th&gt;Cost per interaction&lt;/th&gt;
&lt;th&gt;Typical availability&lt;/th&gt;
&lt;th&gt;Setup time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Human agent (part-time or owner)&lt;/td&gt;
&lt;td&gt;$6 to $15&lt;/td&gt;
&lt;td&gt;Business hours only&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI chatbot (no-code SaaS)&lt;/td&gt;
&lt;td&gt;$0.50 to $0.70&lt;/td&gt;
&lt;td&gt;24/7&lt;/td&gt;
&lt;td&gt;Minutes to a few hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI chatbot (custom-built)&lt;/td&gt;
&lt;td&gt;Varies by volume&lt;/td&gt;
&lt;td&gt;24/7&lt;/td&gt;
&lt;td&gt;Days to weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Industry reports consistently put first-year ROI for small businesses somewhere between 150% and 1,000%, with payback in one to three months for anyone handling a meaningful volume of repeat questions.&lt;/p&gt;

&lt;p&gt;Below roughly 100 conversations a month, fixed platform costs eat most of that advantage, so the math only works once you have enough volume to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 Signals You Actually Need One
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You spend 30 minutes or more a day answering the same handful of questions across email, WhatsApp, or your website&lt;/li&gt;
&lt;li&gt;Your analytics show real traffic arriving after hours or on weekends with no one available to respond&lt;/li&gt;
&lt;li&gt;You are losing leads because a visitor asks a question, gets no answer fast enough, and leaves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of these apply, a chatbot will sit unused. If two or more apply, you are already paying the cost of not having one, you just are not seeing it as a line item. &lt;/p&gt;

&lt;p&gt;For businesses ready to move past support automation into broader workflow automation, our &lt;a href="https://codewithamrendra.vercel.app/services/ai-automation" rel="noopener noreferrer"&gt;AI automation services&lt;/a&gt; team scopes exactly this kind of decision before recommending a build.&lt;/p&gt;

&lt;h2&gt;
  
  
  When It's a Bad Investment
&lt;/h2&gt;

&lt;p&gt;Skip the chatbot if your site gets under 300 to 500 monthly visitors. There is not enough volume for the automation to pay for the platform fee, let alone save meaningful time. &lt;/p&gt;

&lt;p&gt;Skip it too if your sales process depends on a real conversation, high-ticket consulting, complex custom quotes, relationship-driven B2B deals. A bot that mishandles a $20,000 deal because it guessed at an answer costs more than the support hours it saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Most Small Business Chatbots Underperform
&lt;/h2&gt;

&lt;p&gt;A large share of small business chatbots get switched off within the first year despite promising ROI numbers on paper. The reason is rarely the AI model. It is scope. &lt;/p&gt;

&lt;p&gt;Teams point the bot at every question instead of the repetitive ones, skip the escalation path to a human, and never review transcripts to catch bad answers. A narrow bot with a fast handoff to a person outperforms a broad bot with no supervision, every time. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to Decide: A 60-Second Framework
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Count your repetitive interactions per week. Multiply by $6 to $15 to get your current cost.&lt;/li&gt;
&lt;li&gt;Check if that number exceeds $50 to $100 a month, roughly the cost of a no-code chatbot plan.&lt;/li&gt;
&lt;li&gt;If yes, and you have the traffic volume to support it, a chatbot is a reasonable bet.&lt;/li&gt;
&lt;li&gt;If your sales process is high-touch and low-volume, do not automate it. Automate FAQs instead and keep humans on the deals.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Do small businesses actually need an AI chatbot?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not always. It depends on traffic volume and how much time you currently spend on repetitive questions. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. How much does an AI chatbot cost for a small business?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No-code SaaS chatbots typically run $20 to $100 a month, with per-interaction costs around $0.50 to $0.70 compared to $6 to $15 for a human agent handling the same query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What is the typical ROI of an AI chatbot?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Industry benchmarks report first-year ROI ranging from 150% to over 1,000%, depending on conversation volume, deflection rate, and your existing support costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Can a chatbot fully replace human customer support?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. It should handle repetitive, low-complexity questions and escalate anything ambiguous or high-stakes to a human. Bots without an escalation path are the main reason chatbot projects get abandoned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. How long does it take to set up a chatbot?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A no-code SaaS chatbot trained on your existing FAQs or help docs can go live in minutes to a few hours. Custom-built bots integrated with internal tools take longer, typically days to weeks.&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 Frontend developer and Senior technical writer at &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 27+ technical articles on frontend&lt;br&gt;
engineering, system design, and modern web development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/amrendra1998/" 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>chatbots</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Postman vs Insomnia: Which API Testing Tool Wins in 2026</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:37:54 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/postman-vs-insomnia-which-api-testing-tool-wins-in-2026-m9h</link>
      <guid>https://dev.to/codewithamrendra/postman-vs-insomnia-which-api-testing-tool-wins-in-2026-m9h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Postman vs Insomnia&lt;/strong&gt; comes down to storage model and automation depth, not features. Postman ships Newman for CI/CD, richer mock servers, and a paid team model starting at $19/user/month after cutting its free tier to one user in March 2026.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Choosing between Postman and Insomnia in 2026 means picking a storage model and an automation pipeline, not just a UI.&lt;/p&gt;

&lt;p&gt;Both tools cover REST, GraphQL, and basic collaboration, but they diverge hard on CI/CD tooling, pricing at scale, and how they fit into modern engineering workflows, including the &lt;a href="https://codewithamrendra.vercel.app/category/ai-agents" rel="noopener noreferrer"&gt;AI agent tool-calling pipelines&lt;/a&gt; teams are now building around OpenAPI specs. &lt;/p&gt;

&lt;p&gt;This article benchmarks both CLIs in an actual GitHub Actions run, walks through migrating a real collection between the two, and covers pricing changes that shipped earlier this year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postman vs Insomnia at a Glance
&lt;/h2&gt;

&lt;p&gt;Postman is the broader platform. Insomnia is the leaner client with more storage flexibility.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Postman&lt;/th&gt;
&lt;th&gt;Insomnia&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Founded&lt;/td&gt;
&lt;td&gt;2014, originally a Chrome extension&lt;/td&gt;
&lt;td&gt;2016, acquired by Kong in 2019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Proprietary, free tier&lt;/td&gt;
&lt;td&gt;Core is Apache 2.0, open source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocols&lt;/td&gt;
&lt;td&gt;REST, GraphQL, gRPC, WebSocket, MQTT&lt;/td&gt;
&lt;td&gt;REST, GraphQL, gRPC, WebSocket, SSE, SOAP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Cloud-first, account required&lt;/td&gt;
&lt;td&gt;Local Vault, Git Sync, or Cloud Sync per project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;Newman&lt;/td&gt;
&lt;td&gt;inso&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory footprint&lt;/td&gt;
&lt;td&gt;500MB or more, 2-3 second startup&lt;/td&gt;
&lt;td&gt;200-300MB, under 1 second startup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mock servers&lt;/td&gt;
&lt;td&gt;Yes, with request/response simulation&lt;/td&gt;
&lt;td&gt;Yes, via plugin-based mock responses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Insomnia's per-project storage choice is the detail most comparisons skip. A regulated team can keep everything in Local Vault with no account. A platform team can commit collections to the same monorepo as the services they test using Git Sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage Model: Local Vault, Git Sync, and Cloud Sync
&lt;/h2&gt;

&lt;p&gt;Insomnia lets you pick storage per project instead of forcing one model on the whole team. Local Vault keeps collections entirely on-device. Cloud Sync adds optional end-to-end encryption for teams that want centralized access without a Git workflow.&lt;/p&gt;

&lt;p&gt;Postman took the opposite direction. Since it discontinued Scratch Pad, its offline mode, in September 2023, the default workspace experience requires an account and cloud sync. &lt;/p&gt;

&lt;p&gt;There is no equivalent to Git Sync built into the core product. Teams in healthcare, finance, or air-gapped environments that need collections to live in version control alongside code have to route through export/import workflows instead of native sync.&lt;/p&gt;

&lt;p&gt;If your team already lives in Git for everything else, Insomnia's model removes a step. If your team is fine with cloud accounts and wants Postman's monitoring and mock server depth, the storage difference matters less.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD in Practice: Newman vs inso
&lt;/h2&gt;

&lt;p&gt;Postman automates through Newman, its Node-based CLI. Insomnia automates through &lt;code&gt;inso&lt;/code&gt;, bundled as the &lt;code&gt;insomnia-inso&lt;/code&gt; npm package. Both run inside GitHub Actions, GitLab CI, or Jenkins without a GUI.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions Workflow Comparison
&lt;/h3&gt;

&lt;p&gt;Here is a single workflow file running both tools against the same API so you can compare output format and setup cost directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-tests&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postman-newman&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install -g newman&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Postman collection&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;newman run ./collections/orders-api.postman_collection.json \&lt;/span&gt;
            &lt;span class="s"&gt;-e ./environments/staging.postman_environment.json \&lt;/span&gt;
            &lt;span class="s"&gt;--reporters cli,junit \&lt;/span&gt;
            &lt;span class="s"&gt;--reporter-junit-export ./newman-report.xml&lt;/span&gt;

  &lt;span class="na"&gt;insomnia-inso&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install -g insomnia-inso&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Insomnia unit tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;inso run test orders-api \&lt;/span&gt;
            &lt;span class="s"&gt;--workspace ./insomnia \&lt;/span&gt;
            &lt;span class="s"&gt;--env Staging \&lt;/span&gt;
            &lt;span class="s"&gt;--reporter junit \&lt;/span&gt;
            &lt;span class="s"&gt;--output ./inso-report.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both jobs exit non-zero on a failed assertion, so either integrates cleanly into a required status check. &lt;/p&gt;

&lt;p&gt;The practical difference shows up in maintenance: Newman has a larger reporter ecosystem (HTML, Slack, Datadog exporters exist as separate packages), while &lt;code&gt;inso&lt;/code&gt; ships fewer built-in reporters but needs less configuration to point at a Git-synced workspace, since the workspace folder is already a plain directory of files.&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;CLI package&lt;/th&gt;
&lt;th&gt;Config source&lt;/th&gt;
&lt;th&gt;Reporter ecosystem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Newman&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;newman&lt;/code&gt; (npm)&lt;/td&gt;
&lt;td&gt;Exported &lt;code&gt;.postman_collection.json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Large, many third-party reporters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inso&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;insomnia-inso&lt;/code&gt; (npm)&lt;/td&gt;
&lt;td&gt;Native workspace directory (Git Sync friendly)&lt;/td&gt;
&lt;td&gt;Smaller, built-in JUnit/JSON only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For more CI/CD patterns beyond API clients, see the &lt;a href="https://codewithamrendra.vercel.app/category/devops" rel="noopener noreferrer"&gt;DevOps automation guides&lt;/a&gt; on this blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating a Postman Collection to Insomnia
&lt;/h2&gt;

&lt;p&gt;Insomnia's &lt;code&gt;inso&lt;/code&gt; CLI imports Postman v2.1 collections directly. This is the part most comparisons skip entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Convert a Postman v2.1 collection into an Insomnia workspace&lt;/span&gt;
npx insomnia-inso import &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; postman &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--src&lt;/span&gt; ./collections/orders-api.postman_collection.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--workspace&lt;/span&gt; ./insomnia/orders-api.insomnia.json

&lt;span class="c"&gt;# inso writes a new Insomnia workspace file at the target path.&lt;/span&gt;
&lt;span class="c"&gt;# Environment variables map 1:1 between the two formats.&lt;/span&gt;
&lt;span class="c"&gt;# Pre-request scripts and tests written in pm.* syntax do NOT convert automatically.&lt;/span&gt;
&lt;span class="c"&gt;# Rewrite pm.test() assertions using Insomnia's chai-based assertion API before relying on the import.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gap that costs the most time in practice is scripting. Postman's &lt;code&gt;pm.sendRequest&lt;/code&gt;, &lt;code&gt;pm.test&lt;/code&gt;, and &lt;code&gt;pm.environment.set&lt;/code&gt; calls have no direct Insomnia equivalent. Budget time to manually port any collection that leans on pre-request scripts, not just simple request/response pairs.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL and Multi-Protocol Support
&lt;/h2&gt;

&lt;p&gt;Insomnia's GraphQL interface has cleaner schema introspection and autocomplete out of the box, which is why it shows up repeatedly in comparisons as the pick for GraphQL-heavy teams. Postman supports GraphQL too, but the request builder treats it closer to a REST body with a query string rather than as a first-class protocol.&lt;/p&gt;

&lt;p&gt;For gRPC and WebSocket testing, both tools support connection reuse and streaming responses. Insomnia adds native SOAP and Server-Sent Events support, which Postman does not offer directly.&lt;/p&gt;

&lt;p&gt;If your team's API surface is REST-only, this distinction will not affect your decision. If GraphQL or gRPC is a meaningful share of your traffic, test both interfaces against your actual schema before standardizing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing in 2026: What Actually Changed
&lt;/h2&gt;

&lt;p&gt;Postman's Free plan dropped to a single user on March 1, 2026, ending the free multi-user collaboration that many small teams relied on. Any shared workspace now requires a paid Team plan.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan tier&lt;/th&gt;
&lt;th&gt;Postman&lt;/th&gt;
&lt;th&gt;Insomnia&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;1 user only (since March 2026)&lt;/td&gt;
&lt;td&gt;Unlimited users, Hobby/Essentials tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry paid&lt;/td&gt;
&lt;td&gt;Team, $19/user/month&lt;/td&gt;
&lt;td&gt;Solo/Pro, $12/user/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;$49/user/month&lt;/td&gt;
&lt;td&gt;$45/user/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Insomnia still advertises free cloud collaboration for unlimited users on its core plan, which is the opposite direction from Postman's March 2026 change. &lt;/p&gt;

&lt;p&gt;For a 10-person team, that gap alone can run into thousands of dollars a year, so confirm current tier limits on each vendor's pricing page before budgeting, since both companies have changed terms more than once in the past 18 months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using API Collections in AI Agent Workflows
&lt;/h2&gt;

&lt;p&gt;Neither tool markets itself around AI agents, but both export OpenAPI specs, and that export is what makes them useful in tool-calling pipelines. &lt;/p&gt;

&lt;p&gt;If you are building an agent that needs to call your internal API, the OpenAPI spec your Postman or Insomnia collection already produces can be converted directly into a tool schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Convert an exported OpenAPI spec into an LLM tool schema&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;readFileSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./openapi.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paths&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;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;]:&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="kr"&gt;any&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;def&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;methods&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="k"&gt;as&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="kr"&gt;any&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operationId&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\W&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tools.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Each entry now matches the shape an LLM tool-use API expects directly.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production agent systems, this pattern removes the need to hand-write a tool schema for every internal endpoint. Whichever client your team already uses to maintain the collection becomes the source of truth for the agent's tool definitions too, so the API documentation and the agent's capabilities never drift apart. &lt;/p&gt;

&lt;p&gt;This is the one place in this comparison where the choice of Postman or Insomnia genuinely does not matter, since both export a standard OpenAPI document that the conversion script above accepts unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Tool Should You Standardize On
&lt;/h2&gt;

&lt;p&gt;Pick Postman if your team needs Newman's mature reporter ecosystem, built-in monitoring, and mock server depth, and the per-seat pricing at your team size is acceptable after the March 2026 free tier cut.&lt;/p&gt;

&lt;p&gt;Pick Insomnia if your team wants Git-native collection storage, faster local performance, stronger GraphQL and multi-protocol support, or you are trying to avoid Postman's account requirement for offline work.&lt;/p&gt;

&lt;p&gt;Neither choice is permanent. The migration script above makes switching a matter of hours for simple collections, and days for collections with heavy pre-request scripting, not a rebuild from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Is Insomnia better than Postman for GraphQL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Insomnia's GraphQL interface has cleaner schema introspection and query building. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Can you import Postman collections into Insomnia?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, using &lt;code&gt;inso import --type postman&lt;/code&gt;. Requests and environment variables convert directly. Pre-request scripts and &lt;code&gt;pm.test()&lt;/code&gt; assertions do not convert automatically and need to be rewritten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is Insomnia really free?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core client is Apache 2.0 licensed and free for unlimited users on the Hobby or Essentials tier, including Local Vault, Git Sync, and Cloud Sync. The Pro tier at $12/user/month adds RBAC and higher mock request limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Which is better for CI/CD, Newman or inso?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both integrate cleanly into GitHub Actions and exit non-zero on failed assertions. Newman has a larger third-party reporter ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Is Postman still free for teams in 2026?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not for shared workspaces. As of March 1, 2026, Postman's Free plan is limited to one user. Any team collaboration requires the paid Team plan at $19/user/month.&lt;/p&gt;

&lt;p&gt;If this helped, follow along on GitHub for the migration script and CI workflow files used in this comparison: &lt;a href="https://github.com/AmrendraCodes" rel="noopener noreferrer"&gt;github.com/AmrendraCodes&lt;/a&gt;.&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://codewithamrendra.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;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/amrendra1998/" 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>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Stop Using Media Queries for Everything: Learn CSS Container Queries</title>
      <dc:creator>Amrendra kumar</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:52:54 +0000</pubDate>
      <link>https://dev.to/codewithamrendra/stop-using-media-queries-for-everything-learn-css-container-queries-12c5</link>
      <guid>https://dev.to/codewithamrendra/stop-using-media-queries-for-everything-learn-css-container-queries-12c5</guid>
      <description>&lt;p&gt;Every frontend developer reaches for media queries as the default tool for responsive design. For years, that's made sense media queries check the size of the browser viewport and adjust styles accordingly.&lt;/p&gt;

&lt;p&gt;But once you start building smaller, reusable pieces of UI sidebar widgets, product cards, dashboard tiles media queries alone start to fall short. This is where CSS container queries come in, and if you're building components for real projects, they're worth adding to your toolkit today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Introduction
&lt;/h2&gt;

&lt;p&gt;If you're still getting comfortable with component-based frontend development in general, my post on &lt;a href="https://codewithamrendra.vercel.app/blog/how-to-learn-react" rel="noopener noreferrer"&gt;how to learn React&lt;/a&gt; is a good companion read alongside this one.&lt;/p&gt;

&lt;p&gt;In this blog, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Media Queries Syntax&lt;/li&gt;
&lt;li&gt;CSS Media Types&lt;/li&gt;
&lt;li&gt;Enter Container Queries&lt;/li&gt;
&lt;li&gt;Why This Matters for Component-Driven Design&lt;/li&gt;
&lt;li&gt;Browser Support: When to Use Them&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Media Queries Syntax
&lt;/h2&gt;

&lt;p&gt;Media queries let you apply CSS styles based on features of a device or the environment a webpage is displayed in. They're the foundation of responsive web design, and you add them to a stylesheet using the &lt;code&gt;@media&lt;/code&gt; rule.&lt;/p&gt;

&lt;p&gt;A media query consists of an optional media type and one or more media features, each of which resolves to either &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;media-type&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media-feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media-feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* CSS rules to apply */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The media type is optional — but if you use &lt;code&gt;not&lt;/code&gt;, you must specify a media type explicitly.&lt;/p&gt;

&lt;p&gt;The overall query evaluates to &lt;code&gt;true&lt;/code&gt; if the specified media type matches the current device and all listed media features are also true. When that happens, the corresponding style rules are applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Media Types
&lt;/h2&gt;

&lt;p&gt;The optional media type specifies which kind of media the styles are meant for. If it's omitted, it defaults to &lt;code&gt;all&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;Used for all media type devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;print&lt;/td&gt;
&lt;td&gt;Used for print preview mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;screen&lt;/td&gt;
&lt;td&gt;Used for computer screens, tablets, and smartphones&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This works well at the page level. But a card or widget component doesn't actually care how wide the browser window is — it cares how much space &lt;em&gt;it&lt;/em&gt; has been given inside its parent. That's the gap container queries were built to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Container Queries
&lt;/h2&gt;

&lt;p&gt;Container queries flip the logic. Instead of asking "how wide is the browser window?", they ask "how wide is the element's parent container?" This means a single component can adapt intelligently no matter where it's placed on the page.&lt;/p&gt;

&lt;p&gt;Here's the basic syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.card-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;container-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;container-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@container&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things are happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;container-type: inline-size&lt;/code&gt;&lt;/strong&gt; tells the browser to treat this element as a query container, tracking its inline (usually horizontal) size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@container&lt;/code&gt;&lt;/strong&gt; is the at-rule that lets you write conditional styles based on that container's size, not the viewport.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now the &lt;code&gt;.card&lt;/code&gt; component adapts based on the space it's actually given — whether that's a full-width hero section or a 300px sidebar slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Component-Driven Design
&lt;/h2&gt;

&lt;p&gt;Modern frontend development leans heavily on reusable components — design systems, React component libraries, CMS-driven page builders. These components get dropped into all kinds of layouts, often ones the original developer never anticipated.&lt;/p&gt;

&lt;p&gt;Container queries make components genuinely self-sufficient. They don't need to know about the page; they only need to know about their own boundaries.&lt;/p&gt;

&lt;p&gt;That's a meaningful shift in mindset: responsive design stops being purely a page-level concern and becomes something you build directly into individual components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Support and When to Use Them
&lt;/h2&gt;

&lt;p&gt;Container queries now have solid support across all major modern browsers — Chrome, Firefox, Safari, and Edge. For most production use cases today, they're safe to adopt, especially with a graceful fallback in place for older browsers if you need to support legacy environments.&lt;/p&gt;

&lt;p&gt;That said, container queries aren't a full replacement for media queries — they solve a different problem. Use media queries for overall page-level layout decisions (like switching from a single-column to a multi-column layout based on viewport width), and use container queries when a specific component needs to adapt based on its own available space.&lt;/p&gt;

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

&lt;p&gt;Media queries aren't going away, but relying on them for every responsive decision creates unnecessary rigidity. Container queries give developers a more precise, component-first tool — one that matches how modern UIs are actually built.&lt;/p&gt;

&lt;p&gt;If you're designing reusable components, it's worth adding container queries to your toolkit today. Want to go deeper? Check out more frontend breakdowns like this on the &lt;a href="https://codewithamrendra.vercel.app/blog" rel="noopener noreferrer"&gt;CodeWithAmrendra blog&lt;/a&gt;. And if you'd like to talk shop, learn more about my work on the &lt;a href="https://codewithamrendra.vercel.app/about" rel="noopener noreferrer"&gt;about page&lt;/a&gt;, or &lt;a href="https://codewithamrendra.vercel.app/hire-me" rel="noopener noreferrer"&gt;hire me&lt;/a&gt; for your next project. You can also &lt;a href="https://codewithamrendra.vercel.app/contact" rel="noopener noreferrer"&gt;get in touch here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>css</category>
      <category>frontend</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <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/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/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>
  </channel>
</rss>
