Next.js Discord

Discord Forum

Double lookup despite using cache

Unanswered
Wuchang bream posted this in #help-forum
Open in Discord
Wuchang breamOP
In this code resolvePlayer is being called twice, when player was not found in the resolvePlayer method, is there a way to fix that?
import { Metadata } from 'next';
import { redirect } from 'next/navigation';
import { cache } from 'react';
import redis from '~/lib/redis';
import { sanitizeString } from '~/lib/security';

import { fetchAllPlayerStatsAction } from '~/app/private-server-actions/rankingActions';

type PageProps = {
  params: Promise<{ player: string }>;
};

// Function to resolve player name and UUID with caching
const resolvePlayer = cache(async (player: string): Promise<PlayerData> => {
  //code logic
  return { /user/ };
});

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const { player } = await params;
  const playerData = await resolvePlayer(player);

  return {
    title: `${playerData.name}`,
    description: ``,
    keywords: [''],
  };
}

export default async function GraczPage({ params }: PageProps) {
  const { player } = await params;
  const playerData = await resolvePlayer(player);
  
  // If player doesn't exist, redirect to ranking page with notification
  if (!playerData.exists) {
    redirect('/');
  }
  
  // Pre-fetch all player stats server-side with ISR caching
  const playerStats = await fetchAllPlayerStatsAction(playerData.uuid);
  
  return (
    <>
    //view
    </>
  );
}

8 Replies

Western paper wasp
resolvePlayer is called both in generateMetadata and in the page component itself. These are two different render invocations, and cache() doesn’t share the result between them.
You can move data fetching to a single place, for example only in the page and pass it into metadata, or accept this behavior as normal. That’s how App Router and Metadata work.
For full control, use an external cache such as Redis or a database instead of react.cache
Western paper wasp
The pattern from the docs looks similar, but react.cache() isn’t shared between generateMetadata and page. Those are different render contexts. The docs show a memoized fetch, but it will still run twice, once for metadata and once for the page. This is expected behavior in the App Router.
If you need a single real request, move the data fetching into the page and pass it into metadata, or use an external cache such as Redis or a database instead of react.cache.
@Western paper wasp The pattern from the docs looks similar, but `react.cache()` isn’t shared between `generateMetadata` and `page`. Those are different render contexts. The docs show a memoized fetch, but it will still run twice, once for metadata and once for the page. This is expected behavior in the App Router. If you need a single real request, move the data fetching into the page and pass it into metadata, or use an external cache such as Redis or a database instead of `react.cache`.
Greater Shearwater
I don't think that's correct.

The whole purpose of cache() from React is to cache (memoize) the function, so that the function body doesn't run multiple times for the same render. And both metadata and the page functions run in the same render, so the fetch will only run one time, thanks to the cache().

For example, I tested with the below setup now, and the console log only gets printed one time. Not twice. Even though the function is called in metaData and as well as in the page.

import { Metadata } from "next"
import { cache } from "react"

const getSomething = cache(async () => {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  console.log("Function executed.")
})

export async function generateMetadata(): Promise<Metadata> {
  await getSomething()
  return {
    description: "Description",
    title: "Title",
  }
}

export default async function TestPage() {
  await getSomething()
  return <h1>Hello, World!</h1>
}

And if I remove the cache, then the console log gets printed twice.

Happy to be corrected if I am missing something.
Western paper wasp
You’re right that react.cache() does memoize a call within a single render pass, and in a simple generateMetadata + page example it can look like a single call. My previous reply was about real world behavior in the App Router, where generateMetadata and page are not guaranteed to share the exact same render context in all modes (RSC, streaming, prefetch, ISR, edge). In those cases, the react.cache() result may not be reused, and the request can be executed again. So if you need strictly one real network request, the safe option is to move the fetch into page and pass the data into metadata, or use an external cache (Redis/DB) instead of relying on react.cache().
Western paper wasp
react.cache() caches only within a single render,generateMetadata() and page can be rendered separately (different contexts, streaming, ISR, prefetch). That’s why the function may be called twice, and this is normal in the App Router.
How to guarantee a single real request:
- Perform the request only in page
- Reuse the result for both the page and metadata (via arguments/object)
- Or use an external cache (Redis/DB)

react.cache() is not a guarantee of a single request. For 100% certainty, use a single fetch or an external cache