Next.js Discord

Discord Forum

Confused about Next.js 15 caching

Unanswered
Mozzy posted this in #help-forum
Open in Discord
I understand that in 15, the default cache is no-store, but now I cannot opt into caching at all in some cases.

The following code are server actions. getTopTracks is called from a server page.tsx
const getAccessToken = async () => {
  const response = await fetch(TOKEN_ENDPOINT, {
    method: "POST",
    headers: {
      Authorization: `Basic ${basic}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: querystring.stringify({
      grant_type: "refresh_token",
      refresh_token,
    }),
  });

  return response.json();
};

export const getTopTracks = async (range: string) => {
  const { access_token } = await getAccessToken();

  const url = new URL("https://api.spotify.com/v1/me/top/tracks");

  // Excluding medium_term as it's the default
  const validRange = ["short_term", "long_term"].includes(range);
  if (validRange) url.searchParams.append("time_range", range);

  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
    // next: { revalidate: 86_400 },
    // With the above or with the below, this is never cached
    cache: "force-cache",
  });

  return res.ok
    ? ((await res.json()) as SpotifyApi.UsersTopTracksResponse)
    : null;
};

I am able to use cache: "force-cache" on getAccessToken, but adding it to getTopTracks leads to it always skipping cache when the page is navigated to
 │ GET https://api.spotify.com/v1/me/top/tracks 200 in 288ms (cache skip)
 │ │ Cache skipped reason: (cache-control: no-cache (hard refresh))

Any pointers would be helpful

11 Replies

Where is cache-control: no-cache (hard refresh) coming from and why does the cache property not override this?
I read that staleTimes have been set to 0 as the default. I assume that's where "hard refresh" comes from. But why would the cache: "force-cache" on the fetch() not be followed?
You are setting the Authorization header on the fetch call. This will never be cached.
Would this be correct then?
export const getTopTracks = unstable_cache(
  async (range: string) => {
    console.log("Fetching top tracks");

    const { access_token } = await getAccessToken();

    const url = new URL("https://api.spotify.com/v1/me/top/tracks");

    // Excluding medium_term as it's the default
    const validRange = ["short_term", "long_term"].includes(range);
    if (validRange) url.searchParams.append("time_range", range);

    const res = await fetch(url, {
      headers: {
        Authorization: `Bearer ${access_token}`,
      },
    });

    return res.ok
      ? ((await res.json()) as SpotifyApi.UsersTopTracksResponse)
      : null;
  },
  [],
  // 3 seconds as a test. Seems to work
  { revalidate: 3  },
);
It seems to work, but I'm unsure if that's how to do it
What I meant was, any fetch call that sets the Authorization header is not cacheable via the options object.

If you want to cache that fetch call you will need to look into using unstable_cache to wrap that fetch call.
Oh nvm you are using that
didn't see it up there lol
Yeah that will work
Just keep in mind that you are caching the entire response there, which is fine if your application uses the entire response, otherwise you should filter it down to the parts you need and only return those.

Only the data that is returned from the function is cached.
True, thanks