Caching is one of the big draws for people using the Next.js framework. Its on-by-default, “just works” nature sets you up for high performance applications right out of the gate. However, improving web performance comes at the cost of a complex caching system. This complexity is a source silent errors in the form of stale and incorrect data. Next.js does its best to choose the right caching behavior for each page. Sometimes, it chooses wrong, resulting in unexpectedly stale pages or even pages that are slower than they should be.

Next.js caching is remarkable in that it caches so many things in so many places. Everything from fetch requests to rendered HTML and RSC payloads for hydrating an entire page are cached when possible. To add to the complexity, Next.js caches at many levels with build time, server and browser side caches.

Note: All examples use the App Router and Next.js version 15 unless otherwise stated. However, much of this applies to version 14 as well.

Understanding NextJS Caching

What Does Next.js Cache?

Next.js does its best to cache as much as it can for as long as it can. There are three main things that are cached: fetch() response data, rendered HTML and React Server Component(RSC) Payloads.

Where Does Next.js Cache Data?

Next.js caches different kinds of data in all layers of the application using four main mechanisms. The Data Cache, Request Memoization and the Full Route Cache are all used to cache data on the server while the Router Cache caches data in the browser. Not all caches are populated at run-time. The Full Route Cache even caches data during the build process by becoming part of the build artifact.

All these different data types, mechanisms and locations make for some confusion. You can generally determine where and what your data cache is caching your data by considering the kind of data you’re working with:

Data Type Cache Mechanism Where Stored
fetch() responses Data Cache Build, Server
  Request Memoization Server
HTML Full Route Cache Build, Server
RSC Payloads Full Route Cache Build, Server
  Router Cache Client

How Does Next.js Handle Stale Cache?

Next.js uses a “fetch for next time” strategy when handling stale data. This results in the user seeing some stale information. The cache is only checked for stale data when a request is made for that data. When stale data is discovered, it is returned as-is while fresh data is fetched in the background. This keeps pages fast, but causes expired data to display to the user at least once before it is reloaded.

fetch() Caching

If there’s one thing Next.js loves caching, it’s data from API calls. Generally speaking, requests to external API’s are made using javascript’s built-in Fetch API. Next.js wraps native fetch() to automatically store calls and return them from the cache.

Requests are cached at run-time on the server and also at build time when rendering static pages. Fetch caching is performed by two separate mechanisms within Next.js with the difference being how long the cache is valid.

Don’t use fetch? Some things like SQL and other data sources have their own client libraries which don’t use fetch(). Next.js provides a cache() function for these situations. cache() allows any expensive or slow call to be cached while still benefiting from Next.js’s builtin caching behaviors.

Two mechanisms are used to cache fetch responses depending on whether the data is for same-request caching a longer term caching:

Same Request Caching

Duplicate requests made within a single page load are cached by Next.js Request Memoization. This sounds fancy, but it’s just a per-request cache for fetch() and cache() calls. It lets you make requests in different components without worrying about duplicated requests. When Next.js sees that multiple requests share the same destination and arguments, only one request will be sent to the API. The other calls will be returned directly from the Request Memoization cache. You no longer need to pass a single response data from parent to child components to improve performance! The data in the cache will not be re-used once the request lifecycle has ended.

Cross Request Caching

When data should be used in multple requests, Next.js stores it in the Data Cache. The Data Cache saves fetch response data just like Request Memoization, but reuses responses in more than one page load. A cached response can be valid forever, invalidated after a specified amount of time, or invalidated on demand.

HTML Caching

HTML for static pages is pre-rendered during the build and stored in the “Full Route Cache” as part of the build artifacts. This HTML is returned on initial page load to quickly show the content of the page. Client side components are then re-hydrated to finish building the page.

Just like fetch() caching, cached HTML can be valid forever, invalidated after a specified amount of time, or invalidated on demand depending how it is configured. The Next.js server renders and caches HTML at runtime in cases where the cache is configured to expire.

React Server Component Payloads (RSC)

React Server Component Payloads (RSC) are also generated at build time or run-time on the server. Cached RSC payloads work very similarly to cached HTML, but are used for subsequent Single Page Application (SPA) style page navigations. After the initial full page load, Next.js navigates to new pages by using the browser’s History API and DOM manipulation.

What is a RSC payload? RSC stands for React Server Component. RSC payloads describe everything about a page and contain pre-rendered Server Components, client component placeholders and props passed to client components from the server. Next.js uses this data to hydrate new pages without doing a full page load.

RSC payloads are cached by two mechanisms depending on where the payload is cached:

The Server

The Full Route Cache stores RSC payloads and pre-rendered HTML. The server uses it to quickly return pre-built RSC payloads to the browser. Those payloads are generated at build time for static pages. For pages that are re-validated periodically, the payload is rebuilt and stored in the cache at runtime by the Next.js server.

The Browser

RSC payloads are the one data type that Next.js also caches in the browser. These payloads are stored in the Next.js Router Cache. The Router Cache re-uses RSC payloads when going back and forward through history and when navigating to a new page whose RSC payload has been pre-fetched. No request is made to the server when the cache has the necessary payload.

Static Pages as Cache

When coming from other JavaScript frameworks, Next.js Static Pages are rather confusing. Static pages effectively work as a cache but they cache the final HTML not just the data that was used to build the page.

To add to the confusion, these static pages are executed and cached at build time. Next.js automatically opts in pages to static caching when it thinks they do not use any dynamic inputs. As an example, this basic Next.js page is static, resulting in surprising behavior:

export default async function Page() {
    return <div>
        <h1>This page is statically rendered</h1>
        <div>
            {/* The rendered date will never change! */}
            {/* In fact, the date displayed will be the date the application was built! */}
            Rendered at: {new Date().toString()}
        </div>
    </div>
}
page.tsx - Next.js static page by default. Unexpected results!

Common Caching Examples

Although Next.js tries its best to make the best caching choice for each page, it will be wrong sometimes. In those cases, you can specify explicitly how you would like pages and data cached.

How to Force a Page To Render Dynamically

A page can be configured to render dynamically even when Next.js heuristics have determined it should be a static page:

// Tell Next.js to always render this page dynamically
export const dynamic = 'force-dynamic'

export default async function Page() {
    return <div>
        <h1>This page is dynamically rendered</h1>
        <div>
            {/* The rendered date will update every page load! */}
            Rendered at: {new Date().toString()}
        </div>
    </div>
}
page.tsx - Next.js page forced to render dynamically

How to Force a Page To Render Statically

Forcing a page to render statically is much less common, but Next.js has a configuration for it when needed:

// Tell Next.js to always render this page statically
export const dynamic = 'force-static'
Place in page.tsx - Next.js page forced to render statically

How to Re-render a Static Page After A Specified Time

Some pages don’t need up to the second updates, but just need to be refreshed occasionally. Next.js can be told to occasionally refresh a static page.

// Tell Next.js to re-render this static page every hour
// In seconds, 60 sec * 60 minutes/hr = 3600 seconds
export const revalidate = 3600
Place in page.tsx - Next.js page forced to render statically

How to Rerender a Static Page On Demand

Next.js clears the cached page HTML and RSC payloads and re-renders them when a page’s path is revalidated.

Note Caches cannot be cleared inside render functions. revalidatePath() must be called inside a Server Action or Route Handler.

// Inside a Server Action or Route Handler
revalidatePath('/path/to/static/page')
Forcing a static page to re-render using revalidatePath()

How to Enable fetch() Caching

Starting in Next.js 15, fetch requests are un-cached by default. You must opt into caching even when making GET requests. In Next.js 14 and below, only GET requests were cached by default. Use the 'force-cache' option when making a request that should be cached:

export const dynamic = 'force-dynamic'

export default async function Page() {
    var response = await fetch('https://api.example.com/...', { cache: 'force-cache' })
    var data = await response.json()

    return <div>
		    This fetched data is cached by Next.js: {data.exampleString}
    </div>
}
page.tsx - Enable fetch cache with "force-cache"

How to Disable fetch() Caching

Some requests should never be cached. Use the 'no-store' cache setting to disable caching for that request:

// Tell Next.js to never cache this fetch request
var response = await fetch('https://api.example.com/...', { cache: 'no-store' })
Uncached fetch request in Next.js

How to Cache fetch() Responses For A Specific Amount of Time

Requests don’t have to be cached forever. Next.js can cache requests for a certain amount of time before re-fetching them. The Next specific revalidate setting specifies the lifetime of the cached response:

// Tell Next.js to cache this fetch request for a minute
// The setting is in seconds
var response = await fetch('https://api.example.com/...', { next: { revalidate: 60 } })
Mark the fetch response as stale after 60 seconds

How to Clear fetch() Cache On Demand

Sometimes your application knows when data has become stale. In those cases, you can tell Next.js to clear the cache when needed. You must tell Next.js which cached data should be cleared. Data can be cleared by the path of the page or by tags placed in the fetch requests.

How to Clear Cached Data By Page

All cache used by a page can be cleared using the page’s path.

Note Caches cannot be cleared inside render functions. revalidatePath() must be called inside a Server Action or Route Handler.

// 1. Make a cached fetch() request on page '/my/page'
var response = await fetch('https://api.example.com/...', { cache: 'force-cache' })

// 2. Later, inside a Server Action or Route Handler
revalidatePath('/my/page')
Mark all data used by the page as stale using revalidatePath()

How to Clear Cached Data By Tag

Clearing by tags lets you re-fetch a type of data regardless of what page it was loaded on.

Note Caches cannot be cleared inside render functions. revalidateTag() must be called inside a Server Action or Route Handler.

// 1. Make a cached fetch() request on any page
var response = await fetch('https://api.example.com/...', { cache: 'force-cache', next: { tags: ['example-api'] } })

// 2. Later, inside a Server Action or Route Handler
revalidateTag('example-api')
Mark all tagged data as stale using revalidateTag()

How to Clear All Cached Data

The entire Next.js cache can be cleared by revalidating the root path:

// Inside a Server Action or Route Handler
// Clear ALL cached data
revalidatePath('/')
Revalidate the entire Next.js cache

How to Cache Data When Not Using fetch()

Next.js can cache data even when fetch() is not used to retrieve it. This is commonly used for database requests. Code must opt-in to this caching by using the react cache() function:

import { cache } from 'react'
import { expensiveCall } from 'lib'

// Wrap our expensive call in react's cache function
export const getItem = cache(async (id: string) => {
  const item = await expensiveCall(id)
  return item
})
code.ts - Cache expensive calls with the cache() function

Common Problems with Caching in Next.js

If this post didn’t make it obvious, Next.js caching is pretty complex. This complexity causes silent errors in the form of stale content from over caching and poorly performing pages from under caching. Some of these problems are pretty easy to stumble into:

Changes To Caching Behavior in Next.js 15+

Next.js version 14 and below cached fetches and GET route handlers by default. This resulted in unintuitive default behaviors. Because of developer confusion, version 15+ does not cache fetches or route handlers by default. Fetch calls and route handlers must now opt-in to caching.

Users See Stale Pages And Data in Next.js

Stale data will be displayed to users at least some of the time when a page uses any of Next.js’s caching features. This is caused by two Next.js caching behaviors. First, stale data is only checked when a page is requested. And second, to refresh stale data, Next.js returns the stale data for the current request and fills the cache with new data later in the background. Read more

Next.js Pages Rendering Statically

All pages are treated as static by default. Next.js has heuristics to detect dynamic pages, but they are not perfect. When the heuristics fail, Next.js renders static pages at build time and never again. Here’s an example of a static page causing unexpected results. This issue is resolved with 'force-dynamic' which tells the page to render dynamically.

Next.js Always Caches Duplicate Fetch Requests in a Single Page Load

In rare instances, a page makes the same request multiple times, expecting all requests to be sent to the server. Next.js’s “Request Memoization” cache will always cache all requests after the first!

export const dynamic = 'force-dynamic' // Always render dynamically

export default async function Page() {
    // This request is not cached by default (In Next.js 15)
    var response = await fetch('https://api.example.com/data')

    // Request Memoization will always return the cached result from the first request
    var response2 = await fetch('https://api.example.com/data')

    return <div>I fetched some data!</div>
}
page.tsx - Request Memoization cannot be disabled

Avoid ALL Next.js fetch caching by using the unwrapped fetch:

var response = await fetch._nextOriginalFetch('https://api.example.com/data')
var response2 = await fetch._nextOriginalFetch('https://api.example.com/data')
It is possible to avoid all Next.js caching

Next.js has very aggressive client side link prefetching. All links visible in the browser’s viewport are prefetched from the server. This can result in a large number of requests being made as soon as a page finishes loading. While beneficial for

Remember the Prod Build! This behavior does not occur in development mode.

export default async function Page() {
    return <div>
        <h1>All these links will be prefetched by Next.js as soon as the page finishes loading!
        <div>
            <Link href="/here">go here</Link>
            <Link href="/there">go there</Link>
            <Link href="/everywhere">go everywhere</Link>
        </div>
    </div>
}
page.tsx - Links are prefetched when they become visible

Next.js Production Builds Behave Differently than Development

When running in development mode, Next.js does not cache as heavily as in the production build. This can cause a page to work perfectly during development but then be cached too heavily when run in production. Be sure to test caching behaviors in Next.js production builds when testing cache behaviors.

# Running in development mode is quick and easy, but doesn't show full caching behavior:
npm run dev
# -or-
next dev

# Running a product build will enable all Next.js's caching features:
npm run build
npm run start
# -or-
next build
next start
Next development mode vs production build

Build Next.js Apps with Confidence!

Bugs are inevitable, even in modern frameworks with fancy caching features. Having a system to automatically capture and report errors when they happen is key to having confidence in your development cycle, and your products.

TrackJS error monitoring fully supports Next.js, and all other JavaScript technologies. Built by developers for developers.

Did you like this?
VP Engineering TrackJS

What to do Next:

1. Try TrackJS on Your Website

TrackJS gives you the visibility to find and fix your errors before users find them. Get started in 5 minutes tracking errors with all the context you'll need to squash the important bugs in your app.

2. Get the Debugger Newsletter

Join The Debugger for amazing JavaScript tips, debugging walkthroughs, news, and product releases for Request Metrics. No more than once a week, probably a lot less.