Next.js 16 use cache works locally but not on Vercel deployment for dynamic server function

I am using Next.js 16 with App Router and cacheComponents: true deployed on Vercel.

I have two cached server functions:

  • getMenu() → uses "use cache" and caches correctly on both localhost and Vercel.
  • getHoroscope(day, sign) → also uses "use cache" with cacheLife("horoscope"), but on Vercel it executes on every request instead of returning cached data.

Expected behavior

The getHoroscope() function should be cached according to the configured cacheLife, and subsequent requests for the same day and sign should return the cached result.

Actual behavior

  • :white_check_mark: Local development: cache works as expected.
  • :cross_mark: Vercel deployment: getHoroscope() executes on every request.
  • :white_check_mark: getMenu() caching works correctly on the same deployment.

Route

/horoscopes/[day]/[sign]

Example:

https://darshan24-frontend-next.vercel.app/horoscopes/daily/aries

Cache configuration

cacheComponents: true,

cacheLife: {
  menus: {
    stale: 2592000,
    revalidate: 2592000,
    expire: 2592000,
  },
  horoscope: {
    stale: 86400,
    revalidate: 3600,
    expire: 604800,
  },
}

Cached function

export async function getHoroscope(day: string, sign: string) {
  "use cache";

  cacheLife("horoscope");

  const res = await fetch(
    `${process.env.API_URL}wp-json/v1/horoscopes/get-data?day=${day}&rashi=${sign}`
  );

  return res.json();
}

Additional information

  • The data comes from a WordPress REST API.
  • The API returns deterministic data for the same day and sign.
  • No cookies(), headers(), or draftMode() are used in the cached function.
  • The menu cache works correctly in the same application, so caching is not completely broken.
  • The issue only affects the horoscope data function on Vercel.

Has anyone experienced this with use cache on dynamic routes, or is there any known limitation or configuration required on Vercel?

Hi Anurag,

I’d check whether getHoroscope() is being cached at request time rather than during prerender/static shell generation.

The local vs Vercel difference can make this confusing: locally you usually have one long-running server process, so an in-memory use cache entry is easy to hit. On Vercel, if that dynamic route is evaluated at runtime, requests can land on different function instances/regions, so the default in-memory use cache entry may not be reused the same way. That would also explain why getMenu() works while getHoroscope(day, sign) does not: the menu may be part of a more static/shared path, while the horoscope route is running per dynamic route request.

If all day / sign combinations are known, I’d try prerendering the finite set:

export async function generateStaticParams() {
  return [
    { day: "daily", sign: "aries" },
    { day: "daily", sign: "taurus" },
    // include all valid day/sign combinations
  ]
}

Then keep the cache directive close to the actual data access:

import { cacheLife } from "next/cache"

export async function getHoroscope(day: string, sign: string) {
  "use cache"
  cacheLife("horoscope")

  const res = await fetch(
    `${process.env.API_URL}wp-json/v1/horoscopes/get-data?day=${day}&rashi=${sign}`
  )

  return res.json()
}

If the route has to stay runtime/dynamic and you need the result shared across Vercel invocations, I’d test use cache: remote for this function instead of plain use cache:

export async function getHoroscope(day: string, sign: string) {
  "use cache: remote"
  cacheLife("horoscope")

  const res = await fetch(
    `${process.env.API_URL}wp-json/v1/horoscopes/get-data?day=${day}&rashi=${sign}`
  )

  return res.json()
}

One quick sanity check: log only day, sign, and a timestamp inside getHoroscope() for a short test. If every refresh logs a new execution on Vercel but not locally, and use cache: remote reduces that, then the issue is likely runtime cache behavior rather than the cache key being wrong.