Overview
Learn about caching mechanisms in Next.js.
Caching is a critical aspect of modern web applications, enhancing performance by reducing redundant computations, minimizing server load, and improving response times. Next.js provides several caching mechanisms tailored for various use cases, including server-side and client-side caching strategies.
Use Case | Recommended Strategy |
---|---|
Server-side disk cache | unstable_cache or "use cache" (canary) |
Server-side deduplication | React.cache |
Client-side in-memory cache | SWR , React Query |
Filesystem cache | CDN, Edge caching |
Disk Caching
Server-side disk caching helps reduce redundant computations and significantly improves response times by storing computed data for reuse. This guide explains how to use unstable_cache
for disk-based caching and its integration into your application workflow.
Option 1: unstable_cache
Next.js provides with unstable_cache
a powerful tool for disk-based caching.
The method consists of three core arguments:
- fetchFunction: The function used to retrieve data.
- keyParts: An array of unique identifiers for the cache entry to prevent data conflicts.
- options: Additional settings, such as
tags
, to group related cache entries and streamline cache management.
Good to know: This option is marked as legacy in favor of "use cache". However "use cache" is not out of canary yet. The same concept still applies, but keyParts will become implicit.
Basic Example
Here is an example of using unstable_cache
to fetch and cache contact data:
import { unstable_cache } from 'next/cache';async function getContacts(params: GetContactsParams): Promise<ContactDto[]> { return unstable_cache( fetchContactsFromDatabase, [ 'organizations', session.organization.id, 'contacts', params.pageIndex, params.pageSize, params.searchQuery ], { tags: [`organizations:${session.organization.id}:contacts`] } );}
Type-Safe access
For better maintainability (no loose strings) and type safety, you can use caching helpers to generate keys and tags dynamically. Here is how you can integrate a helper:
import { unstable_cache } from 'next/cache';import { Caching, OrganizationCacheKey } from '@/data/caching';async function getContacts(params: GetContactsParams): Promise<ContactDto[]> { return unstable_cache( fetchContactsFromDatabase, Caching.createOrganizationKeyParts( OrganizationCacheKey.Contacts, session.organization.id, params.pageIndex, params.pageSize, params.searchQuery ), { tags: [ Caching.createOrganizationTag( OrganizationCacheKey.Contacts, session.organization.id ) ] } );}
Cache invalidation
After a mutation operation, such as adding a new contact, you can revalidate related cache entries by calling their associated tags:
import { revalidateTag } from 'next/cache';import { authOrganizationActionClient } from '~/actions/safe-action';import { addContactSchema } from '~/schemas/contacts/add-contact-schema';export const addContact = authOrganizationActionClient .metadata({ actionName: 'addContact' }) .schema(addContactSchema) .action(async ({ parsedInput, ctx }) => { // TODO: implement add contact.. revalidateTag( Caching.createOrganizationTag( OrganizationCacheKey.Contacts, ctx.organization.id ) ); });
This ensures the cache is refreshed and includes the latest changes.
Cache namespaces
In Achromatic we defined two cache namespaces, depending if it is for a user or organization.
- OrganizationCacheKey: Contains all organization cache keys
- UserCacheKey: Contains all user cache keys.
Note that Next.js doesn't give you any helpers for define namespaces, it's all on us to generate the correct keys.
Option 2: "use cache"
The codebase is compatible with the experimental "use cache"
directive from the Next.js canary release. Once this feature becomes stable, a codemod will be provided to seamlessly migrate your codebase from unstable_cache
to "use cache"
. Benefits of "use cache":
- Simplifies the caching mechanism by making it more declarative.
- Reduces boilerplate code for caching logic.
- Enhances developer experience with clearer syntax.
Stay tuned for updates and ensure your implementation aligns with the evolving standards in Next.js.
Deduplication
Deduplication prevents redundant operations for identical requests by reusing cached results. This technique enhances performance and reduces unnecessary processing.
React.cache
is an in-memory caching mechanism designed to deduplicate function calls within the same request. When invoked with identical parameters, it returns cached results instead of performing the same operation again. This can significantly improve efficiency in your React applications.
Example: Deduplicating auth
from Auth.js
A common use case for React.cache
is deduplicating calls to authentication methods like the auth
function from Auth.js
. Here's how you can achieve this:
import { cache } from 'react';export const dedupedAuth = cache(auth);
Calling dedupedAuth
multiple times within a single request ensures it only runs once, with subsequent calls using the cached result. This approach enhances efficiency and resource utilization, particularly beneficial in scenarios with parallel routes.