Cache-Control: no-store in Next.js: Why Nothing Caches
By Ugur Saritepe · August 9, 2026
Your page sends a Cache-Control: no-store header, so every visit — every user, and every crawler fetch — rebuilds it from scratch instead of serving a saved copy. On Vercel that shows up as an x-vercel-cache: MISS that never turns into a HIT, and on Next.js it is almost always something in your own code putting it there. First, the good news: this does not hide you from Google. It's a speed and cost problem, not the reason a site is missing from search. Here's how to confirm it and turn it off.
What no-store actually means
Cache-Control: no-storetells every cache between your server and the visitor to never save a copy of the response: not the browser, not the CDN (the content-delivery network, the layer of edge servers like Vercel's that sits in front of your app), not any in-between proxy. Every request must therefore be answered by a fresh render from your server. Cache-Controlitself is the HTTP response header carrying that instruction, a line your server sends back with every page telling browsers and networks how long they're allowed to reuse the response, and no-store is its strictest value.
The header you were likely flagged on is the full dynamic default: private, no-cache, no-store, max-age=0, must-revalidate. Three of those words say “don't reuse this” from three angles. The one that costs you is no-store: because nothing is ever stored, there is never a cached copy to serve, so the next request has no choice but to render the page all over again on the server.
no-store vs no-cache vs max-age=0: which one blocks caching?
Only no-store forbids keeping a copy. The other two directives allow a saved copy but control when it may be reused:
- no-store: nothing may be saved anywhere, so every request is a full server render. This is the only directive of the three that guarantees no cached copy exists.
- no-cache: a copy may be saved, but it must be revalidated with the server before it is reused. Paired with an
ETag, that revalidation is a cheap304 Not Modifiedinstead of a full render. - max-age=0, must-revalidate: practically the same as
no-cache: the copy is stale the moment it arrives and must be checked with the server before reuse.
The practical consequence: if your goal is “visitors never see stale content”, no-cache plus an ETag already gives you that without paying a full render per request. Reserve no-store for responses that must never be written to any cache at all, such as pages with per-user or sensitive data.
Why it matters — and what it does not do
A no-store homepage is slower, more expensive, and crawled less efficiently than a cached one. But it is notwhy you're invisible in search. Keep those two facts apart, because the internet routinely conflates them.
- Slower for real users. A cached page is handed over in milliseconds from an edge server near the visitor. A
no-storepage waits for your server to render it from scratch on every single request, which shows up as a worse time to first byte. - Every crawl re-downloads the full page.Here's the twist: Googlebot ignores
no-storeitself. Google's crawler documentation says its crawlers support caching only throughETagandLast-Modified, and that “other HTTP caching directives aren't supported.” The real cost travels with the header: a page rebuilt on every request ships without anETag— the fingerprint a crawler sends back to ask “has this changed?” and get a cheap304 Not Modifiedinstead of the whole page. No fingerprint, no shortcut: Google re-downloads the full page on every single crawl, which is exactly the waste its December 2024 note on HTTP caching asks site owners to avoid. - Higher serverless cost. Every uncached request runs your server function again. On usage-billed hosting like Vercel, a homepage that could have been served from cache for free instead bills you for a function invocation on every hit, including every bot.
no-store does notdo is de-index you. To Google's crawlers the directive simply doesn't exist — they honor only ETag and Last-Modified— and nothing in Google's documentation connects Cache-Control to indexing or ranking. The header tells caches what to do; it says nothing about whether a page belongs in search results. If your pages are missing from search, the cause is elsewhere — start with the indexing triage, not this header.How to see it in 30 seconds
Read the response headers straight off your live URL. One line in a terminal:
curl -sI https://yourdomain.com | grep -i "cache-control\|x-vercel-cache\|etag"
Bad output — the page is never cached, and there is no etag line at all:
cache-control: private, no-cache, no-store, max-age=0, must-revalidate x-vercel-cache: MISS
Good output — no-store is gone, and the page carries a fingerprint:
cache-control: public, max-age=0, must-revalidate etag: "590aa7636ee1ff4f4e06faff02dd20a2" x-vercel-cache: HIT
The tell is x-vercel-cache. On Vercel that header reports whether the edge served a stored copy: HIT means it came from cache, MISS means it was rendered fresh, PRERENDER means it came from a page built at deploy time, and STALE means a cached copy was served while a new one rendered in the background. A healthy cached page shows MISS on the first request after a deploy, then HIT on the ones after it. A no-store page shows MISS every single time, because there is nothing to hit. The missing etagis the second tell: cacheable pages travel with that fingerprint, per-request pages don't. If you'd rather not use the terminal, open your site, press F12, go to the Network tab, reload, click the top document request, and read the same lines under Response Headers.
The root cause on Next.js and Vercel
On a Next.js site, a no-store header almost always means the page was forced into dynamic rendering — rebuilt per request — instead of being rendered once at build time. Next.js sends that dynamic default deliberately, so a page holding per-user data never gets cached and leaked to the next visitor. The problem is when a plain marketing homepage gets swept into dynamic rendering by accident. As of July 2026, four triggers do it:
- A force-dynamic export.
export const dynamic = "force-dynamic"anywhere in the route opts the whole page out of caching, full stop. - Reading request data. Calling
cookies()orheaders(), or readingsearchParams, in a Server Component tells Next.js the output depends on the incoming request, so it can't be cached. - An explicitly uncached fetch. A
fetch(url, { cache: "no-store" })— orexport const revalidate = 0— inside the route marks it dynamic too. Note the word explicitly: a fetch with nocacheoption at all does not do this. Since Next.js 15 its result isn't cached between requests, but the page itself still prerenders — only spelling outcache: "no-store"forces the whole route dynamic. - An analytics or A/B snippet in your root layout that reads a cookie or header. Because the layout wraps every page, one such call can force your entire site dynamic — this is the sneakiest of the four.
One genuinely dynamic case is correct and should stay no-store: a logged-in dashboard, a cart, anything that shows a different thing to each visitor. The fix below is only for pages that look the same for everyone — homepages, blog posts, marketing pages — which is exactly where an accidental no-store costs you for nothing.
The fix: let the page render once and cache
Find the trigger and remove it, then confirm the page is static or on a revalidation schedule. Work in this order:
- Delete the accidental trigger. Drop the
force-dynamicexport if the page doesn't need it. Move anycookies()/headers()read out of the shared layout and into only the component that truly needs per-request data. Removecache: "no-store"from fetches whose data doesn't change per visitor. - Pick static or timed refresh. With no dynamic trigger, the page renders once at build and is served from cache — the right default for content that rarely changes. If it changes on a schedule (say a list that updates hourly), keep it cached but refresh it with
export const revalidate = 3600— that's incremental static regeneration, which re-renders in the background every N seconds while still serving a cached copy in between. - Redeploy and re-check. Run the same
curlfrom the diagnosis step. Theno-storeshould be gone, andx-vercel-cacheshould showHITon the second and later requests.
The verdict: fix exactly one thing this week
Run these in order and stop at the first hit:
curl -sIshowsno-storeon a page that looks the same for every visitor → find and remove the dynamic trigger. This is the whole fix for a marketing homepage.- The header comes from a
cookies()orheaders()call in your root layout → move it out of the layout so it stops forcing every page dynamic. - The page genuinely needs per-request data (dashboard, cart, logged-in view) → leave
no-storein place. It's correct here, and nothing needs fixing.
Prefer this diagnosis done for you?
The deep analysis covers your 3 strongest pages and your search data analyzed by hand, a comparison against the sites that outrank you, and a prioritized fix list for your traffic problem. 10 EUR, delivered to your email within 2 days. It sits alongside the free tools listed on the pricing page.
How to confirm the fix worked
Re-run the header check from a fresh terminal after your deploy finishes:
curl -sI https://yourdomain.com | grep -i "cache-control\|x-vercel-cache\|etag"
Request it twice. The first fetch after a deploy may still read MISS while the edge fills its cache; the second should read HIT (or PRERENDER for a fully static page), no-store should be absent from the cache-control line, and an etag line should now be present. That flip from a permanent MISS to a repeating HIT is the proof — the page is now being served from cache instead of rebuilt on every request.
If you'd rather not keep a curl incantation in your shell history, the TrafficTriage MCP server exposes the same header and rendering checks as tools your AI assistant can call. Ask Claude or Cursor to re-check the page after a deploy and it reports back in the chat that already has your next.config open.
From experience: rarer than the forums suggest, invisible when it hits
Of the 24 sites we ran through the Triage engine in the first week of July 2026, exactly one homepage sent Cache-Control: no-store. On that report it was the top finding — but only because the other seven checkscame back healthy, and even then it scored Monitor, not Critical. The site wasn't missing from Google because of this header, and in our data no site so far has been. That ratio is worth holding onto, because forum threads routinely blame caching for invisibility it doesn't cause.
What made that one site worth flagging is how silent the problem was. The page looked perfectly normal in a browser. The only symptoms lived in the response headers: x-vercel-cache: MISS on every fetch we sent — we requested it twice to be sure — and no etagline at all. The owner had no visual cue that every visit, bots included, was paying for a fresh server render. That's the nature of this problem: it never shows up on screen, only in the headers, so nobody looks until the hosting bill or the load times force the question.
FAQ
Does Cache-Control: no-store hurt my Google ranking?
No. Google's crawlers ignore no-store entirely — per Google's crawler documentation they honor only ETag and Last-Modified for caching, and nothing in Google's documentation connects Cache-Control to indexing or ranking. A no-store page gets re-downloaded in full on every crawl, which wastes effort on a big site, but it stays crawled and indexed like any other page.
Why does Vercel show MISS on every request?
Because no-store tells the edge cache not to keep a copy, so there is never anything stored to serve — every request is a MISS by definition. Once the page is cacheable, you'll see a MISS on the first request after a deploy and HIT on the ones after it.
What's the difference between no-cache and no-store?
no-store means don't save a copy at all — refetch from the server every time. no-cache is softer: a copy may be stored, but it must be revalidated with the server before it's reused. The dynamic default sends both; no-store is the one that forces a full re-render each request.
Should every page be cached?
No. Pages that show different content per visitor — a logged-in dashboard, a shopping cart, anything personalized — should stay no-store so one person's data never gets served to another. Cache the pages that are identical for everyone: homepage, marketing pages, blog posts.
Not sure what's slowing your site down?
Get a free Triage Report: eight checks run from your URL alone — how machine-readable and cacheable your pages are among them — each scored Critical / Monitor / Healthy, ending with the one thing to fix this week. It renders on your screen in about half a minute — no signup, no email required.
