unstable_noStore()
Opt a component out of static rendering so its data is fetched on every request.
unstable_noStore(): void- Added in Next.js v14.0.0
- Deprecated since PHP v15.0.0
Return value
voidNothing. The enclosing scope is marked as uncacheable and opted out of prerendering.
Changelog
- PHP v14.0.0unstable_noStore introduced.
- PHP v15.0.0Deprecated in favor of connection().
unstable_noStore declaratively opts the enclosing scope out of prerendering and marks it as not cacheable. It was the answer for data sources that do not go through fetch — an ORM query, an SDK call — where you could not simply pass cache: 'no-store'.
Status
This is a legacy API, still supported for backward compatibility. Since version 15 the recommended replacement is connection() from next/server, which expresses the same intent — wait for an actual request before rendering this — without the caching-era naming.
What trips people up
- It is per-component, which is the reason to prefer it over
export const dynamic = 'force-dynamic': the rest of the route can still be prerendered. People who reach for the segment config instead lose static rendering for the whole page. - Inside unstable_cache it does not opt out of static generation — the cache configuration wins. Calling it there to "force freshness" silently does nothing.
- It is equivalent to
cache: 'no-store'on a fetch, so mixing both is redundant rather than doubly effective.
Example
import { unstable_noStore as noStore } from 'next/cache'
import { db } from '@/lib/db'
export default async function LiveTotal() {
noStore()
const result = await db.orders.aggregate({ _sum: { amount: true } })
return <p>Total: {result._sum.amount}</p>
}When to use it
- Keep one live figure uncached on an otherwise static page, in a Next.js 14 codebase.
- Opt a database-backed component out of prerendering where no fetch call exists to configure.
- Replace a route-wide dynamic = 'force-dynamic' with a narrower, per-component opt-out.
Frequently asked questions
What replaces unstable_noStore in Next.js 15?
connection() from next/server. It is the recommended replacement and is awaited rather than called synchronously; unstable_noStore remains supported for backward compatibility but is a legacy API.
Is unstable_noStore better than export const dynamic = 'force-dynamic'?
Yes for narrow opt-outs, because it is granular and can be used on a per-component basis while the segment config applies to the whole route. The rest of the page can still be prerendered around the one component that needs request-time data.
Why isn't unstable_noStore making my data fresh?
If the call sits inside unstable_cache, it does not opt out of static generation — the cache configuration determines whether the result is cached. Move the query out of the cached wrapper, or drop the wrapper entirely.
More examples
The recommended replacement
The same component written for Next.js 15 and later. connection() is awaited rather than called synchronously, and it states the actual requirement — this render needs a real incoming request — instead of describing a cache mode.
import { connection } from 'next/server'
import { db } from '@/lib/db'
export default async function LiveTotal() {
// Defer to request time
await connection()
const result = await db.orders.aggregate({ _sum: { amount: true } })
return <p>Total: {result._sum.amount}</p>
}Narrow opt-out inside a static page
Lives at app/dashboard/page.tsx. Only LiveTotal is excluded from the prerender; the shell around it is still static. This is the difference from export const dynamic = 'force-dynamic', which would make the entire route dynamic.
import { Suspense } from 'react'
import LiveTotal from './live-total'
export default function DashboardPage() {
return (
<main>
<h1>Dashboard</h1>
{/* This heading and layout stay prerendered */}
<Suspense fallback={<p>Loading total...</p>}>
<LiveTotal />
</Suspense>
</main>
)
}Where it has no effect
Shows the trap. Inside unstable_cache the call does not opt out of static generation — the result is still cached for 60 seconds. If you need fresh data, do not wrap the function in the first place.
import { unstable_cache, unstable_noStore as noStore } from 'next/cache'
import { db } from '@/lib/db'
export const getStats = unstable_cache(
async () => {
noStore() // ignored here: the cache configuration decides
return db.stats.findFirst()
},
['stats'],
{ revalidate: 60 }
)
Discussion