<?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: Cherry Harambe</title>
    <description>The latest articles on DEV Community by Cherry Harambe (@cherryslist).</description>
    <link>https://dev.to/cherryslist</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%2F4141946%2Ffc56468a-c0d7-4c26-9513-a1faedc1e390.jpg</url>
      <title>DEV Community: Cherry Harambe</title>
      <link>https://dev.to/cherryslist</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cherryslist"/>
    <language>en</language>
    <item>
      <title>ISR pages aren't stale because ISR is broken. They're stale because nobody visited them.</title>
      <dc:creator>Cherry Harambe</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:16:14 +0000</pubDate>
      <link>https://dev.to/cherryslist/isr-pages-arent-stale-because-isr-is-broken-theyre-stale-because-nobody-visited-them-1ic7</link>
      <guid>https://dev.to/cherryslist/isr-pages-arent-stale-because-isr-is-broken-theyre-stale-because-nobody-visited-them-1ic7</guid>
      <description>&lt;p&gt;I run a Next.js content site on Vercel. A few hundred pages, &lt;code&gt;revalidate&lt;/code&gt; set to 300 seconds, App Router, nothing exotic. I changed a number on one of the detail pages, waited the five minutes, reloaded, and saw the old number. Waited an hour. Still the old number.&lt;/p&gt;

&lt;p&gt;My first instinct was that ISR was broken. It wasn't. The behaviour is documented, it's just easy to misread — and the thing that finally made it obvious was reading the response headers instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the headers before you debug anything else
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;curl -I&lt;/code&gt; against any page served by Next.js on Vercel gives you enough to diagnose this in about ten seconds:&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="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://example.com/some-detail-page | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'age|x-vercel-cache|x-nextjs'&lt;/span&gt;

age: 529343
x-nextjs-prerender: 1
x-nextjs-stale-time: 300
x-vercel-cache: STALE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-nextjs-prerender: 1&lt;/code&gt;&lt;/strong&gt; — this page was prerendered, so ISR applies to it. If this header is missing, you're looking at a dynamically rendered page and none of the rest matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-nextjs-stale-time: 300&lt;/code&gt;&lt;/strong&gt; — your &lt;code&gt;revalidate&lt;/code&gt; value, in seconds. This is the &lt;em&gt;window&lt;/em&gt;, not a schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-vercel-cache&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;HIT&lt;/code&gt; means the edge served a fresh copy, &lt;code&gt;STALE&lt;/code&gt; means it served an expired copy and kicked off a regeneration, &lt;code&gt;MISS&lt;/code&gt; means it had to go to the origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;age&lt;/code&gt;&lt;/strong&gt; — how many seconds ago this cached entry was created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;code&gt;age: 529343&lt;/code&gt; is just over six days. On a page whose revalidate window is five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the numbers don't match
&lt;/h2&gt;

&lt;p&gt;ISR regeneration is lazy and request-triggered. There is no background job walking your routes and refreshing them on a timer.&lt;/p&gt;

&lt;p&gt;What actually happens when a request arrives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edge checks the cached entry's age against &lt;code&gt;stale-time&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it's inside the window — &lt;code&gt;HIT&lt;/code&gt;, serve it, done.&lt;/li&gt;
&lt;li&gt;If it's outside the window — serve the &lt;strong&gt;stale&lt;/strong&gt; copy immediately, and &lt;em&gt;start&lt;/em&gt; a regeneration in the background.&lt;/li&gt;
&lt;li&gt;The next request, some time later, gets the fresh copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Read step 3 again. The request that discovers the page is stale does not get fresh content. It gets the old content and pays the cost of triggering the rebuild for whoever comes next.&lt;/p&gt;

&lt;p&gt;Now apply that to a site with a long tail. Here's what I measured across five routes on the same deployment, all with the same 300-second revalidate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;&lt;code&gt;age&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;x-vercel-cache&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64s&lt;/td&gt;
&lt;td&gt;HIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,945s&lt;/td&gt;
&lt;td&gt;STALE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/index-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,945s&lt;/td&gt;
&lt;td&gt;STALE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/detail/one-item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;529,343s&lt;/td&gt;
&lt;td&gt;STALE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/about&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,207,507s&lt;/td&gt;
&lt;td&gt;HIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The homepage is fine — it gets traffic constantly, so it's always within a few minutes of fresh. The detail page is six days old and the about page is fourteen days old, because almost nobody requests them. They aren't being refreshed because the refresh only happens when someone asks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;revalidate: 300&lt;/code&gt; does not mean "this page is at most 5 minutes old". It means &lt;strong&gt;"once someone requests this page more than 5 minutes after it was cached, start rebuilding it."&lt;/strong&gt; For a popular page those are nearly the same thing. For a long-tail page they are wildly different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which is usually fine, until it isn't
&lt;/h2&gt;

&lt;p&gt;For most content this is exactly the behaviour you want. Nobody is served a slow page, nobody hits your origin, and pages that matter stay fresh because traffic keeps them fresh.&lt;/p&gt;

&lt;p&gt;It stops being fine when the content is something people act on. Prices. Availability. Scores and rankings. Anything where a six-day-old page is not merely out of date but actively misleading. On &lt;a href="https://www.cherryslist.ai/" rel="noopener noreferrer"&gt;the site I run&lt;/a&gt; the detail pages carry ratings and pricing that change, and "correct within five minutes" turned out to be "correct whenever someone last happened to load it".&lt;/p&gt;

&lt;p&gt;The other place it bites: you ship a fix, check the page, see the old version, and conclude the deploy failed. It didn't. You were the request that triggered the rebuild, and you'll see your change on the next load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use on-demand revalidation for content you control.&lt;/strong&gt; This is the real answer. When the underlying data changes, tell Next.js explicitly instead of hoping a visitor shows up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;revalidatePath&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="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;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&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="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/detail/&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&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="na"&gt;revalidated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from whatever writes the data — your CMS webhook, your admin action, your import script. The page rebuilds because something changed, which is the correct trigger, rather than because a stranger happened to load it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;revalidateTag&lt;/code&gt; is the better tool when one change affects several routes. Tag the fetches, invalidate the tag, and every page built from that data goes at once:&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;// in the page&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;tags&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;catalogue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// wherever the data changes&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;catalogue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't just lower &lt;code&gt;revalidate&lt;/code&gt;.&lt;/strong&gt; Dropping it from 300 to 60 changes nothing for a page nobody visits — it's still stale until the next request, whenever that is. It only adds load to pages that were already fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check &lt;code&gt;age&lt;/code&gt; in CI or a cron.&lt;/strong&gt; A small script that curls your important routes and alerts when &lt;code&gt;age&lt;/code&gt; exceeds some multiple of &lt;code&gt;stale-time&lt;/code&gt; will catch this long before a user does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;x-vercel-cache: STALE&lt;/code&gt; with a large &lt;code&gt;age&lt;/code&gt; isn't a bug report. It's telling you the page is unvisited. If freshness matters for that route, stop waiting for traffic to trigger the rebuild and revalidate on demand when the data changes.&lt;/p&gt;




</description>
      <category>nextjs</category>
      <category>devops</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your ISR pages aren't stale because ISR is broken. They're stale because nobody visited them.</title>
      <dc:creator>Cherry Harambe</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:11:02 +0000</pubDate>
      <link>https://dev.to/cherryslist/your-isr-pages-arent-stale-because-isr-is-broken-theyre-stale-because-nobody-visited-them-339o</link>
      <guid>https://dev.to/cherryslist/your-isr-pages-arent-stale-because-isr-is-broken-theyre-stale-because-nobody-visited-them-339o</guid>
      <description>&lt;p&gt;I run a Next.js content site on Vercel. A few hundred pages, &lt;code&gt;revalidate&lt;/code&gt; set to 300 seconds, App Router, nothing exotic. I changed a number on one of the detail pages, waited the five minutes, reloaded, and saw the old number. Waited an hour. Still the old number.&lt;/p&gt;

&lt;p&gt;My first instinct was that ISR was broken. It wasn't. The behaviour is documented, it's just easy to misread — and the thing that finally made it obvious was reading the response headers instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the headers before you debug anything else
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;curl -I&lt;/code&gt; against any page served by Next.js on Vercel gives you enough to diagnose this in about ten seconds:&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="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://example.com/some-detail-page | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'age|x-vercel-cache|x-nextjs'&lt;/span&gt;

age: 529343
x-nextjs-prerender: 1
x-nextjs-stale-time: 300
x-vercel-cache: STALE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-nextjs-prerender: 1&lt;/code&gt;&lt;/strong&gt; — this page was prerendered, so ISR applies to it. If this header is missing, you're looking at a dynamically rendered page and none of the rest matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-nextjs-stale-time: 300&lt;/code&gt;&lt;/strong&gt; — your &lt;code&gt;revalidate&lt;/code&gt; value, in seconds. This is the &lt;em&gt;window&lt;/em&gt;, not a schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-vercel-cache&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;HIT&lt;/code&gt; means the edge served a fresh copy, &lt;code&gt;STALE&lt;/code&gt; means it served an expired copy and kicked off a regeneration, &lt;code&gt;MISS&lt;/code&gt; means it had to go to the origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;age&lt;/code&gt;&lt;/strong&gt; — how many seconds ago this cached entry was created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;code&gt;age: 529343&lt;/code&gt; is just over six days. On a page whose revalidate window is five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the numbers don't match
&lt;/h2&gt;

&lt;p&gt;ISR regeneration is lazy and request-triggered. There is no background job walking your routes and refreshing them on a timer.&lt;/p&gt;

&lt;p&gt;What actually happens when a request arrives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edge checks the cached entry's age against &lt;code&gt;stale-time&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it's inside the window — &lt;code&gt;HIT&lt;/code&gt;, serve it, done.&lt;/li&gt;
&lt;li&gt;If it's outside the window — serve the &lt;strong&gt;stale&lt;/strong&gt; copy immediately, and &lt;em&gt;start&lt;/em&gt; a regeneration in the background.&lt;/li&gt;
&lt;li&gt;The next request, some time later, gets the fresh copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Read step 3 again. The request that discovers the page is stale does not get fresh content. It gets the old content and pays the cost of triggering the rebuild for whoever comes next.&lt;/p&gt;

&lt;p&gt;Now apply that to a site with a long tail. Here's what I measured across five routes on the same deployment, all with the same 300-second revalidate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;&lt;code&gt;age&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;x-vercel-cache&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64s&lt;/td&gt;
&lt;td&gt;HIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,945s&lt;/td&gt;
&lt;td&gt;STALE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/index-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,945s&lt;/td&gt;
&lt;td&gt;STALE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/detail/one-item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;529,343s&lt;/td&gt;
&lt;td&gt;STALE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/about&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,207,507s&lt;/td&gt;
&lt;td&gt;HIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The homepage is fine — it gets traffic constantly, so it's always within a few minutes of fresh. The detail page is six days old and the about page is fourteen days old, because almost nobody requests them. They aren't being refreshed because the refresh only happens when someone asks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;revalidate: 300&lt;/code&gt; does not mean "this page is at most 5 minutes old". It means &lt;strong&gt;"once someone requests this page more than 5 minutes after it was cached, start rebuilding it."&lt;/strong&gt; For a popular page those are nearly the same thing. For a long-tail page they are wildly different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which is usually fine, until it isn't
&lt;/h2&gt;

&lt;p&gt;For most content this is exactly the behaviour you want. Nobody is served a slow page, nobody hits your origin, and pages that matter stay fresh because traffic keeps them fresh.&lt;/p&gt;

&lt;p&gt;It stops being fine when the content is something people act on. Prices. Availability. Scores and rankings. Anything where a six-day-old page is not merely out of date but actively misleading. On &lt;a href="https://www.cherryslist.ai/" rel="noopener noreferrer"&gt;the site I run&lt;/a&gt; the detail pages carry ratings and pricing that change, and "correct within five minutes" turned out to be "correct whenever someone last happened to load it".&lt;/p&gt;

&lt;p&gt;The other place it bites: you ship a fix, check the page, see the old version, and conclude the deploy failed. It didn't. You were the request that triggered the rebuild, and you'll see your change on the next load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use on-demand revalidation for content you control.&lt;/strong&gt; This is the real answer. When the underlying data changes, tell Next.js explicitly instead of hoping a visitor shows up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;revalidatePath&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="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;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&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="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/detail/&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&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="na"&gt;revalidated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from whatever writes the data — your CMS webhook, your admin action, your import script. The page rebuilds because something changed, which is the correct trigger, rather than because a stranger happened to load it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;revalidateTag&lt;/code&gt; is the better tool when one change affects several routes. Tag the fetches, invalidate the tag, and every page built from that data goes at once:&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;// in the page&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;tags&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;catalogue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// wherever the data changes&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;catalogue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't just lower &lt;code&gt;revalidate&lt;/code&gt;.&lt;/strong&gt; Dropping it from 300 to 60 changes nothing for a page nobody visits — it's still stale until the next request, whenever that is. It only adds load to pages that were already fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check &lt;code&gt;age&lt;/code&gt; in CI or a cron.&lt;/strong&gt; A small script that curls your important routes and alerts when &lt;code&gt;age&lt;/code&gt; exceeds some multiple of &lt;code&gt;stale-tnextjs,webdev,vercel,performance,ime&lt;/code&gt; will catch this long before a user does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;x-vercel-cache: STALE&lt;/code&gt; with a large &lt;code&gt;age&lt;/code&gt; isn't a bug report. It's telling you the page is unvisited. If freshness matters for that route, stop waiting for traffic to trigger the rebuild and revalidate on demand when the data changes.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

</description>
      <category>backend</category>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Welcome</title>
      <dc:creator>Cherry Harambe</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:04:55 +0000</pubDate>
      <link>https://dev.to/cherryslist/welcome-1gh6</link>
      <guid>https://dev.to/cherryslist/welcome-1gh6</guid>
      <description>&lt;p&gt;Hey all - building a Next.js content site and spending most of my time on the indexing and Core Web Vitals side of it. Here to learn from people who've shipped bigger ones.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>performance</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
