Common Errors in Next.js Caching
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:
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:
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:
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.
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.
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:
How to Disable fetch()
Caching
Some requests should never be cached. Use the 'no-store'
cache setting to disable caching for that request:
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:
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.
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.
How to Clear All Cached Data
The entire Next.js cache can be cleared by revalidating the root path:
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:
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!
Avoid ALL Next.js fetch caching by using the unwrapped fetch:
Next.js Making Requests For Every Visible Link
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.
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.
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.