Preloading and Deduplication

The Same Data in Many Components

The layout, the page and generateMetadata often need the same product. Instead of passing it down through props, each one calls getProduct(id). Duplicate fetch GET calls in one render are memoised automatically; for ORM calls wrap the function in React's cache().

import 'server-only';
import { cache } from 'react';
import { prisma } from '@/lib/prisma';

export const getProduct = cache(async (id: number) => {
  return prisma.product.findUnique({ where: { id } });
});

How cache() Works

Example

A per-request memo: the first call starts the work; later calls with the same argument get the same Promise.

function cache<A extends string | number, R>(fn: (arg: A) => Promise<R>) {
  const memo = new Map<A, Promise<R>>();
  return (arg: A): Promise<R> => {
    if (!memo.has(arg)) memo.set(arg, fn(arg));
    return memo.get(arg)!;
  };
}

let dbQueries = 0;
const getProduct = cache(async (id: number) => {
  dbQueries++;
  return { id, name: 'Product ' + id };
});

async function main() {
  // generateMetadata, layout and page all ask for product 42
  const [a, b, c] = await Promise.all([getProduct(42), getProduct(42), getProduct(42)]);
  await getProduct(7);
  console.log(a.name, b === c ? '(same object)' : '(different)');
  console.log('DB queries:', dbQueries);
}
main();

Output

Product 42 (same object)
DB queries: 2

Preload Pattern

Start a request early, before a check that must run first, and use the result later:

export const preload = (id: number) => {
  void getProduct(id); // start now, do not await
};

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const id = Number((await params).id);
  preload(id); // begins loading the product
  const allowed = await checkRegionAvailability(); // runs at the same time
  if (!allowed) return <p>Not available in your region</p>;
  const product = await getProduct(id); // usually already done
  return <h1>{product?.name}</h1>;
}