How Caching Works in Next.js
- Author: Md. Saad
- Published at: November 03, 2024
- Updated at: December 02, 2024
Caching is the process of storing copies of files in a cache or temporary storage location so that they can be accessed more quickly. Technically, a cache is any temporary storage location for copies of files or data, but the term is often used in Internet technologies.
In recent updates, Next.js has brought significant changes in performance optimisation, and caching plays a pivotal role in enhancing user experience, lowering latency, and reducing server load. In this blog, we’ll explore how caching works in Next.js, covering its different layers, caching strategies, and best practices for making your Next.js application blazing fast.
Catching Process in Next.js
By default, Next.js maximises caching to enhance performance and reduce costs. This results in routes being statically rendered and data requests being cached unless explicitly disabled. The diagram below illustrates the default caching behaviour, including when a route is statically generated during build time and when a static route is accessed for the first time.
Caching mechanisms work in four ways. These are request memorization, data Data Cache, full route cache, and router cache. Here’s a detailed breakdown of the caching mechanisms:
Request Memoization in React
React automatically memoizes fetch requests with the same URL and options. This means you can call a fetch function multiple times across a component tree without triggering multiple network requests.
Features
- What: Return values of functions.
- Where: Server-side.
- Purpose: To reuse data in a React component tree during the lifecycle of a request. This avoids recalculating or re-fetching the same data for multiple components within the same request.
- Duration: It lasts for the duration of the request lifecycle, meaning the memoized data is discarded once the request is completed.
Deduplicated Fetch Requests
For example, if you need the same data in multiple components, you don't have to fetch it at the top level and pass it down. Instead, you can fetch data directly within each component, and React ensures the request is made only once:
async function getItem() {
const res = await fetch('https://.../item/1');
return res.json();
}
const item = await getItem(); // First call: cache MISS
const item = await getItem(); // Second call: cache HIT
How It Works:
- On the first call, the request is a cache MISS and fetches data from the external source.
- The result is stored in memory.
- Subsequent requests within the same render pass are cache HITs, returning the memoized data without re-fetching.
- Once rendering completes, memoization entries are cleared.
Key Points:
- Memoization only applies to GET requests.
- It works for server components (e.g., generateMetadata, Pages, Layouts).
- It doesn't apply to Route Handlers or requests outside the React component tree.
- The cache only lasts during a single server request/render pass.
Opting Out:
Memoization can't be opted out of for GET requests, but for managing individual requests, you can use the signal property from AbortController to abort in-flight requests.
Data Cache in Next.js
Next.js has a built-in Data Cache that persists data fetched via the fetch API across server requests and deployments. This optimises performance by allowing server-side fetch requests to interact with a persistent cache.
Features
- What: cached data (e.g., API responses, database queries).
- Where: Server-side, typically stored in a storage solution like Redis, Memcached, or even local file storage.
- Purpose: To store data across user requests and deployments to avoid frequent fetching from the source. This is useful for reducing server load and latency and improving performance. The cache can be revalidated to ensure fresh data is provided when needed.
- Duration: persistent, meaning it can last indefinitely or for a set period. The cache can be revalidated based on certain conditions, like TTL (Time to Live) or ETag validation.
How the Data Cache Works:
- On the first request, Next.js checks the Data Cache for a cached response.
- If found, it's returned immediately and memoized for the render pass.
- If not, the data is fetched, cached, and memoized.
- For uncached data (e.g., { cache: 'no-store' }), the data is always fetched from the source.
Data Cache vs. Memoization:
- Data Cache persists across requests and deployments, while memoization lasts only for the duration of a request.
Revalidating Cached Data:
- Time-based: Revalidate after a set time (e.g., { next: { revalidate: 3600 } }).
- On-demand: Revalidate by path (revalidatePath) or cache tag (revalidateTag).
How Revalidation Works:
- Time-based: Cached data remains until the time expires, after which a background revalidation is triggered. Stale data is served until fresh data is fetched.
- On-demand: Triggers immediate cache invalidation and fetches fresh data.
Opting Out of Caching:
- Set { cache: 'no-store' } in fetch to always fetch fresh data.
- Use dynamic = 'force-dynamic' to opt out of caching for route segments.
Note: Data Cache is available in pages/routes but not in middleware. If deployed on Vercel, consult Vercel Data Cache documentation for platform-specific features.
Full Route Cache in Next.js
Next.js automatically caches routes at build time for faster page loads. This process, often called Automatic Static Optimization or Static Rendering, caches rendered routes on the server, enabling cached responses instead of server-side rendering for each request.
Features
- What: Cached HTML output and React Server Components (RSC) payloads.
- Where: Server-side.
- Purpose: To reduce the cost of rendering and improve overall performance. By caching full HTML responses or RSC payloads, the server avoids regenerating the same content for every request. This is especially useful for SSR (Server-Side Rendering) in React.
- Duration: Persistent, but it can be revalidated based on specific rules, like when content changes or when a certain time threshold is reached.
How It Works:
- React Server Rendering: Next.js uses React’s APIs to render route segments in chunks on the server. The result is split into two parts:
- React Server Component Payload: A compact binary format containing the rendered result of Server Components, placeholders for Client Components, and references to their JavaScript files.
- HTML: Rendered HTML is streamed as each chunk is completed, without waiting for the entire page to be ready.
- Server Caching (Full Route Cache): Next.js caches the React Server Component Payload and HTML of a route on the server, either at build time or after revalidation.
- Client Hydration: On the client side, the HTML provides a fast, non-interactive preview. The React Server Component Payload is used to update the DOM, and JavaScript hydrates the Client Components to make them interactive.
- Router Cache (Client-side): The React Server Component Payload is cached in-memory on the client. This cache improves navigation performance by storing previously visited routes and prefetching future routes.
- Subsequent Navigations: For future navigations, Next.js checks the Router Cache. If a route segment is cached, it skips server requests. Otherwise, it fetches the necessary data from the server and populates the client cache.
Static vs. Dynamic Rendering:
- Static Routes: Cached at build time or after revalidation.
- Dynamic Routes: Rendered at request time and not cached.
Cache Duration:
The Full Route Cache persists across requests but is cleared on new deployments.
Invalidation:
- Data Revalidation: Triggers server re-rendering and updates the Router Cache.
- Redeployment: Clears the Full Route Cache.
Opting Out:
- Dynamic Rendering: Use dynamic = 'force-dynamic' or revalidate = 0 to skip both the Full Route and Data Cache.
- Uncached Fetch Requests: Opting out of caching for specific fetch requests dynamically renders the route.
Router Cache in Next.js
The Router Cache (also called Client-side or Prefetch Cache) is an in-memory cache in the browser that stores the React Server Component Payload for individual route segments during a user session. It improves navigation by caching visited routes and prefetching potential future routes.
Features
- What: RSC (React Server Component) payload.
- Where: Client-side (in the browser or in-memory storage).
- Purpose: To reduce the need to make new server requests during navigation by caching RSC payloads. This enhances the performance of client-side routing in React applications, making navigation faster as the data is already available locally.
- Duration: Typically lasts for the duration of the user session or can be time-based. (e.g., cached for a specific amount of time, after which the cache is refreshed).
How It Works:
- Caching: As users navigate between routes, Next.js caches visited segments and prefetches likely next routes (based on <Link> components).
- Benefits:
- Instant navigation between visited routes.
- Fast loading of prefetched routes.
- No full-page reload and the React/browser state is preserved.
Router Cache vs. Full Route Cache:
- Router Cache: This method stores the payload in the browser during a session and applies to both static and dynamic routes.
- Full Route Cache: Stores the payload and HTML on the server across multiple requests but only applies to statically rendered routes.
Cache Duration:
- Session-based: Cache persists during navigation but clears on page refresh.
- Automatic Invalidation:
- Dynamic pages (with default prefetch): not cached.
- Static pages and full prefetch: cache lasts 5 minutes.
You can adjust invalidation times using the experimental staleTimes option.
Cache Invalidation:
- Use router.refresh, revalidatePath, or revalidateTag to invalidate the Router Cache and fetch fresh data manually.
- Cookies: Changing cookies (e.g., for authentication) also invalidates the cache.
Opting Out:
- We can't opt out of the Router Cache but can prevent prefetching by setting <Link prefetch={false}>. However, even with prefetching disabled, segments are cached for 30 seconds for fast navigation between nested segments.
Conclusion
Caching in Next.js is a powerful tool for optimizing your web applications. Whether you’re building static sites, dynamic SSR pages, or using APIs, effective caching strategies can significantly improve performance, reduce server load, and enhance the overall user experience. With new caching capabilities and optimizations in Next.js, it's easier than ever to ensure your application is fast, scalable, and ready for the modern web. Thus, By understanding and applying the caching strategies discussed here, you can take full advantage of Next.js performance features and build highly performant, user-friendly applications.
I hope you got a lot out of reading this. Please do not hesitate to contact StaticMania with any questions or feedback. Happy coding!