Apps
Dashboard

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 CaseRecommended Strategy
Server-side disk cacheunstable_cache or "use cache" (canary)
Server-side deduplicationReact.cache
Client-side in-memory cacheSWR, React Query
Filesystem cacheCDN, 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.

Basic Example

Here is an example of using unstable_cache to fetch and cache contact data:

apps/dashboard/data/contacts/get-contacts.ts
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:

apps/dashboard/data/contacts/get-contacts.ts
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:

apps/dashboard/actions/contacts/add-contact.ts
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:

packages/auth/src/index.ts
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.