#Issue after upgrading to nextJS 16.0.3 from 15.x

1 messages · Page 1 of 1 (latest)

civic ospreyBOT
#

Hi @weak spear. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the knowledge sources, I can address both issues you're experiencing after upgrading to Next.js 16:

1. Meta Tags Rendering in <body> Instead of <head>

This is a known behavior change in Next.js 15.2+ related to streaming metadata. According to Metadata renders inside <body> instead of <head> after Next.js v15.3.1, when generateMetadata() is async or involves network requests, Next.js may stream metadata into the body for non-bot user agents to improve performance.

However, the knowledge sources indicate that for bots and crawlers, metadata should still be parsed correctly from the head. If you're seeing SEO issues, you can try setting htmlLimitedBots in your Next.js config, though one user noted this didn't completely resolve the issue in Static metadata rendered in <body> instead of <head>.

2. Fetch Request Memoization Not Working (Your Main Issue)

This is a significant known issue. The knowledge sources reveal several related problems:

In Next.js 15 → 16 upgrades:

Request Memoization Issues:
According to Request memoization sometimes doesn't work, there are cases where request memoization fails, particularly after page reloads. One user noted: "Same issue on app router 15.2.2. Sometimes memoizing doesnt work properly after hot reload."

#

Between generateMetadata and Components:
Fetch requests not cached between generateMetadata and page component specifically documents that fetch requests are not being memoized between generateMetadata() and page components, even though the Request Memoization documentation states they should be.

Important Context for Next.js 16:
The knowledge sources indicate that Next.js 16 changed caching behavior significantly. From Server requests and latency increased, one user asks: "are you aware that nextjs 16 is cache opt in now? did you read the docks on use cache and dynamicIO settings?"

Potential Solutions

Based on the knowledge sources:

  1. Try the "use cache" directive - One user in Fetch requests not cached between generateMetadata and page component stated: "I have just explored on canary the use cache directive and have found that does appear to solve the issues we were having"

  2. Use React's cache function - The Request Memoization documentation suggests: "For cases where fetch is not suitable (e.g. some database clients, CMS clients, or GraphQL clients), you can use the React cache function to memoize functions."

  3. Consider staying on Next.js 15 - Multiple users in the knowledge sources have reverted to Next.js 15 due to these issues.

#

Unfortunately, the knowledge sources suggest these are known regressions in Next.js 16 without clear resolutions yet. The increased request count appears to be an acknowledged trade-off in Next.js 16's architecture changes.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

is it because most of my routes using dynamic rendering? @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, yes, dynamic rendering could be a significant factor in your fetch memoization issues.

How Dynamic Rendering Affects Caching

According to the Rendering Strategies documentation, a route becomes dynamic when it uses any of these APIs:

  • cookies()
  • headers()
  • connection()
  • draftMode()
  • searchParams prop
  • unstable_noStore
  • fetch with { cache: 'no-store' }

The documentation explicitly states: "Dynamic routes are not cached in the Full Route Cache, but can still use the Data Cache for data requests."

Your Multi-Tenant Architecture

Since you mentioned you have a multi-tenant app with domain-based routing, you're likely using headers() to read the domain from the request. This would automatically opt your routes into dynamic rendering.

From cookies break caching/memoization, a Next.js team member explains:

"In Next, using cookies() on a page opts it into dynamic rendering, which means each request will hit the server. This is explained here."

The same principle applies to headers().

Important Note About Fetch Caching vs. Request Memoization

However, the documentation clarifies that dynamic rendering doesn't prevent request memoization within a single render pass. According to Request Memoization:

"Memoization only applies to the React Component tree, this means:

  • It applies to fetch requests in generateMetadata, generateStaticParams, Layouts, Pages, and other Server Components."
#

So theoretically, even with dynamic rendering, your fetch requests should still be memoized within the same request. The fact that they're not suggests this is part of the broader Next.js 16 regression issues mentioned in my previous response, particularly the Request memoization sometimes doesn't work issue.

What You Can Still Do

According to cookies break caching/memoization:

"You can still opt into fetch caching by passing explicit cache configuration options to your fetch."

Since you're already using revalidate: 3600 in your fetch options, this should enable the Data Cache even in dynamic routes, but it appears this isn't working as expected in Next.js 16.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

so how to resolve it without downgrade the version? @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, here are the solutions you can try without downgrading:

1. Use the "use cache" Directive (Next.js 16 Approach)

Next.js 16 introduced a new caching model. According to the use cache: private documentation, you can use "use cache: private" for functions that need access to runtime APIs like cookies() or headers().

First, enable the feature in your config:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

Then wrap your data fetching function:

import { cacheLife, cacheTag } from 'next/cache'

async function getAgentConfig() {
  'use cache: private'
  cacheTag('agent-config')
  cacheLife({ stale: 3600 })
  
  const domain = await Request.getDomain(); // Can use headers() inside
  return await getEnvByDomain({ domain });
}

According to the documentation: "The 'use cache: private' directive works just like use cache, but allows you to use runtime APIs like cookies, headers, or search params."

2. Use React's cache Function

The React cache function documentation suggests manually memoizing your data fetching:

import { cache } from 'react'

export const getAgentConfig = cache(async () => {
  const domain = await Request.getDomain();
  return await getEnvByDomain({ domain });
});

This ensures the function is only executed once per render pass, even when called from multiple components.

3. Use unstable_cache for Multi-Tenant Scenarios

#

Based on revalidatePath with middleware doesn't work on multi-tenant project, one user successfully worked around multi-tenant caching issues using unstable_cache:

import { unstable_cache } from 'next/cache'

export const getAgentConfig = async () => {
  const domain = await Request.getDomain();
  
  return await unstable_cache(
    async (domain: string) => getEnvByDomain({ domain }),
    ["agent-config", domain],
    {
      tags: [`agent-config-${domain}`],
      revalidate: 3600,
    }
  )(domain);
};

The user noted: "This way we can set the exact key we want our cached data to have, and we can revalidate it with revalidateTag"

Important Considerations

According to use cache: private documentation: "Unlike use cache, private caches are not prerendered statically as they contain personalized data that is not shared between users."

For your multi-tenant app where data varies by domain, "use cache: private" or unstable_cache with domain-specific cache keys would be the most appropriate solutions.

The knowledge sources suggest that Next.js 16's caching model requires explicit opt-in, which is why your automatic memoization stopped working after the upgrade.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

here is the updated.
another api is being memoized and cache.. while for getAgentConfig because of headers maybe so thats why its not memoize and cache? @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, yes, you're correct - the use of headers() in your getAgentConfig function is likely why it's not being memoized while your other API calls are.

Why headers() Breaks Memoization

According to the Dynamic APIs documentation:

"Dynamic APIs like cookies and headers, and the searchParams prop in Pages depend on runtime incoming request information. Using them will opt a route out of the Full Route Cache, in other words, the route will be dynamically rendered."

More specifically, from the Rendering Strategies documentation:

"A route becomes dynamic when it uses any of these APIs:

  • cookies()
  • headers()
  • connection()
  • draftMode()
  • searchParams prop"

The Key Issue

Your getAgentConfig function calls Request.getDomain(), which likely uses headers() to read the domain from the request. This makes the entire function dynamic.

According to Requests not memoized when using cookies, a similar issue was reported where:

"When using cookies, requests are not memoized."

A Next.js team member confirmed this is expected behavior:

"This is expected behavior as described in our docs. In Next, using cookies() on a page opts it into dynamic rendering, which means each request will hit the server."

The same applies to headers().

Why Your Other API Works

Your other API that's being memoized and cached likely doesn't use headers(), cookies(), or any other dynamic APIs, so it can be properly memoized using Next.js's automatic request memoization.

The Solution

#

As I mentioned in my previous response, you need to use one of these approaches for functions that use headers():

  1. "use cache: private" - Specifically designed for this use case
  2. unstable_cache with domain-specific cache keys
  3. React's cache function for request-level memoization

The use cache: private documentation explicitly states it's designed for functions that need access to cookies() or headers(), which is exactly your situation with the domain-based routing.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

please help to give example for unstable_cache
because

Accessing dynamic data sources such as headers or cookies inside a cache scope is not supported. If you need this data inside a cached function use headers outside of the cached function and pass the required dynamic data in as an argument.
This API uses Next.js' built-in Data Cache to persist the result across requests and deployments.

@civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, here's how to use unstable_cache with headers() for your multi-tenant scenario:

Correct Pattern: Read Headers Outside, Pass as Argument

According to the unstable_cache documentation:

"Accessing dynamic data sources such as headers or cookies inside a cache scope is not supported. If you need this data inside a cached function use headers outside of the cached function and pass the required dynamic data in as an argument."

Here's how to apply this to your getAgentConfig:

// agents.utils.ts
import { unstable_cache } from 'next/cache'
import { headers } from 'next/headers'

export const getAgentConfig = async () => {
  // Read headers OUTSIDE the cached function
  const domain = await Request.getDomain(); // This uses headers()
  
  // Pass domain as argument to the cached function
  const getCachedConfig = unstable_cache(
    async (domain: string) => {
      // Inside here, you can only use the domain parameter
      // Do NOT call headers() or cookies() here
      return await getEnvByDomain({ domain });
    },
    ['agent-config', domain], // Cache key includes domain
    {
      tags: [`agent-config-${domain}`],
      revalidate: 3600,
    }
  );
  
  return await getCachedConfig(domain);
};

Example from Documentation

The unstable_cache API reference provides this example:

import { unstable_cache } from 'next/cache'
#

export default async function Page({
params,
}: {
params: Promise<{ userId: string }>
}) {
const { userId } = await params
const getCachedUser = unstable_cache(
async () => {
return { id: userId }
},
[userId], // add the user ID to the cache key
{
tags: ['users'],
revalidate: 60,
}
)

//...
}


## Multi-Tenant Workaround

From [revalidatePath with middleware doesn't work on multi-tenant project](<https://github.com/vercel/next.js/issues/59825>), a user successfully implemented this pattern:

```typescript
export default async function Page(props: {
  params: Promise<{ domain: string; slug: string }>;
}) {
  const { domain, slug } = await props.params;

  const post = await unstable_cache(getPost, ["post", domain, slug], {
    tags: [`post-${domain}-${slug}`],
    revalidate: 300,
  })(domain, slug);

  // ...
}

Key Points

  1. Read headers() or cookies() OUTSIDE the unstable_cache wrapper
  2. Pass the extracted values as arguments to the cached function
  3. Include dynamic values in the cache key (the second parameter array)
  4. Use domain-specific tags for granular revalidation per tenant

This approach allows you to cache the expensive getEnvByDomain call while still supporting your multi-tenant architecture with domain-based routing.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

may i know when next build and next start its memoize and cache while on vercel its not cache and memoized? @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, this is a known issue with how Vercel handles caching differently from local builds. Here's why:

The Core Problem: Vercel's Serverless Architecture

According to a detailed explanation from a Next.js team member in 'use cache' directive not working on Vercel production:

"On Vercel however Next.js is deployed in a serverless context and sharing memory has some complications. Storing the cache entries remotely is possible but this has a cost both in dollars, but also in latency, and this cost may not be appropriate for caches that have high cardinality keys like dynamic params or cookies especially when a HIT is very unlikely. So by default Vercel's dynamic 'use cache' handler is forgetful. It essentially never actually stores the result so it's a miss every time. It's kind of like an LRU with size 0."

Why It Works Locally But Not on Vercel

Local (next build + next start):

  • Runs in a single Node.js process
  • Memory is shared across requests
  • Cache entries persist in memory
  • Memoization works as expected ✅

Vercel Production:

  • Runs in serverless functions
  • Each function invocation may be in a different container
  • Memory is NOT shared across requests
  • Default cache handler is "forgetful" (doesn't store anything) ❌

The Solution: Use "use cache: remote"

From the same issue thread:

"If you, the app author, know that a particular cache is worth storing remotely because it has only low cardinality inputs but just happens to be behind something else dynamic you can instruct the framework by using a named cache handler. Currently the only other default supported cache handler is 'use cache: remote'. On Vercel this will use the Vercel Runtime Cache to store the entries."

#

Multiple users confirmed this works. From 'use cache' directive not working on Vercel production:

"'use cache: remote' fixed it (per one of those above threads)."

Apply This to Your Code

For your getAgentConfig function that uses headers():

import { cacheLife, cacheTag } from 'next/cache'

export const getAgentConfig = async () => {
  'use cache: remote'  // Use remote cache for Vercel
  cacheTag('agent-config')
  cacheLife({ expire: 3600 })
  
  const domain = await Request.getDomain();
  return await getEnvByDomain({ domain });
};

According to the use cache: remote documentation:

"The 'use cache: remote' directive enables caching of shared data in dynamic contexts where regular use cache would not work, for example after calling await connection(), await cookies() or await headers()."

Important Note

The documentation states: "Results are stored in server-side cache handlers and shared across all users." This is appropriate for your multi-tenant scenario where the same domain should return the same config across all requests.

This explains why your local build works perfectly but Vercel doesn't cache - it's an architectural difference in how caching is handled in serverless environments.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

I got error using unstable_cache
Error running the exported Web Handler: Error: Invariant: incrementalCache missing in unstable_cache async e=>await tF({domain:e})
at /var/task/.next/server/chunks/[root-of-the-server]__e47737da..js:22:26328
at tW (/var/task/.next/server/chunks/[root-of-the-server]__e47737da.
.js:33:50470)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async tV (/var/task/.next/server/chunks/[root-of-the-server]__e47737da..js:33:50531)
at async handler (/var/task/.next/server/chunks/[root-of-the-server]__e47737da.
.js:33:51443)
at async /var/task/.next/server/chunks/[root-of-the-server]__e47737da..js:33:35841
at async ta (/var/task/.next/server/chunks/[root-of-the-server]__e47737da.
.js:33:32239)
at async /var/task/___next_launcher.cjs:117:22
at async Object.serve [as handlerWeb] (/var/task/___next_launcher.cjs:112:10)

@civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, this error occurs when unstable_cache is used in contexts where the incremental cache is not available. Here's what's happening and how to fix it:

The Problem

According to "incrementalCache missing in unstable_cache" unhandled rejection when using unstable_cache in middleware, this error occurs when:

Error: Invariant: incrementalCache missing in unstable_cache async () => {

The issue states: "This is reproducible with and without --turbo" and affects both next dev and next start.

Where This Error Occurs

This error typically happens when unstable_cache is used in:

  • Middleware
  • Edge runtime contexts
  • Certain serverless environments where the incremental cache isn't initialized

The Solution: Use "use cache: remote" Instead

Based on your previous conversation and the knowledge sources, you should use "use cache: remote" instead of unstable_cache for your Vercel deployment.

From 'use cache' directive not working on Vercel production, the recommended approach for Vercel is:

// agents.utils.ts
import { cacheLife, cacheTag } from 'next/cache'

export const getAgentConfig = async () => {
  'use cache: remote'
  cacheTag('agent-config')
  cacheLife({ expire: 3600 })
  
  const domain = await Request.getDomain(); // Can use headers() here
  return await getEnvByDomain({ domain });
};

According to the use cache: remote documentation:

"The 'use cache: remote' directive enables caching of shared data in dynamic contexts where regular use cache would not work, for example after calling await connection(), await cookies() or await headers()."

Why This Works

#

The "use cache: remote" directive:

  • Works with headers() and cookies() (unlike regular unstable_cache)
  • Uses Vercel's Runtime Cache in production
  • Doesn't require the incremental cache to be initialized
  • Is specifically designed for serverless/edge environments

This approach avoids the incrementalCache missing error entirely while still providing the caching behavior you need for your multi-tenant application.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

since my proxy.ts calling getAgentConfig, it will lead error @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, you cannot use unstable_cache in middleware or proxy contexts because the incremental cache is not available in those environments.

The Core Issue

According to "incrementalCache missing in unstable_cache" unhandled rejection when using unstable_cache in middleware, this exact error occurs when trying to use unstable_cache in middleware:

Error: Invariant: incrementalCache missing in unstable_cache

The issue explicitly states this happens when using unstable_cache in middleware.

Caching in Middleware/Proxy is Not Supported

From Caching data doesn't work in middleware, the knowledge sources confirm:

"Fetch caching is not supported in proxy. Any fetches done inside of your proxy will be uncached."

This is from the Caching in Next.js documentation.

Workarounds for Middleware/Proxy

The knowledge sources suggest these alternatives:

1. Pass Data via Headers

From Caching data doesn't work in middleware:

export default async function middleware(request) {
  const response = NextResponse.next()

  try {
    const fetchResp = await fetch(
      "<https://www.example.com/...",>
    )
    const data = await fetchResp.json()
  
    if (data) {
      response.headers.set('dataKey', JSON.stringify(data))
    }

    return response
  } catch (error) {
    ...
  }
}

Then retrieve the data in your server or client components from the headers.

2. Use Vercel's Runtime Cache API

From Caching data doesn't work in middleware:

#

"The new Runtime Cache release works in Edge Middleware: https://vercel.com/changelog/introducing-the-runtime-cache-api

This can be used to build a fetch wrapper that behaves like Data Cache, that will work in middleware."

3. Move Logic Out of Middleware

Consider moving your getAgentConfig logic out of the proxy/middleware and into your page components or layouts where "use cache: remote" will work properly.

Summary

Unfortunately, the knowledge sources indicate that caching in middleware/proxy is fundamentally limited in Next.js. You'll need to either:

  • Use the header-passing workaround
  • Implement custom caching with Vercel's Runtime Cache API
  • Restructure your application to call getAgentConfig outside of the proxy context

The "use cache: remote" directive I suggested earlier will only work in Server Components, not in middleware or proxy functions.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

can you check behavior of adding {next: {revalidate: 3600, tags: ["tags"]}} behavior in nextjs 16.0.3?
because when using nextjs 15, when revalidateTag, it worked, but when upgrading to nextjs 16.0.3 not working @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, there are known issues with revalidateTag behavior in Next.js 16 that could explain why your cache revalidation stopped working after upgrading from Next.js 15.

Known Issues with revalidateTag in Next.js 16

1. Signature Change in Next.js 16

According to the How to upgrade to version 16 documentation, revalidateTag has a new function signature in Next.js 16:

'use server'
 
import { revalidateTag } from 'next/cache'
 
export async function updateArticle(articleId: string) {
  // Mark article data as stale
  revalidateTag(`article-${articleId}`, 'max')
}

The documentation states:

"The single-argument form revalidateTag(tag) is deprecated. It currently works if TypeScript errors are suppressed, but this behavior may be removed in a future version. Update to the two-argument signature."

2. Behavior Change: Stale-While-Revalidate

From the revalidateTag API reference, the revalidation behavior now depends on the second argument:

  • With profile="max" (recommended): Uses stale-while-revalidate semantics - serves stale content while fetching fresh content in the background
  • Without the second argument (deprecated): Immediately expires the cache (blocking revalidation)

The documentation notes:

"When using profile="max", revalidateTag marks tagged data as stale, but fresh data is only fetched when pages using that tag are next visited. This means calling revalidateTag will not immediately trigger many revalidations at once."

3. Reported Issues After Upgrading

From revalidateTag no longer works after v14.2.11, users reported similar issues:

#

"Our website was working just fine till upgrading Next to 15.1.6. After upgrade cache revalidation suddenly stopped working."

The issue states: "No errors are logged and it looks like revalidation happens successfully yet the content stays stale."

How to Fix Your Code

Update your revalidateTag calls to use the new two-argument signature:

// Old (Next.js 15) - deprecated in 16
revalidateTag('updated-agent-config')

// New (Next.js 16) - required
revalidateTag('updated-agent-config', 'max')

For your fetch configuration, the next: { revalidate: 3600, tags: [...] } syntax should still work, but you need to update how you call revalidateTag.

Additional Context

From the Caching and Revalidating documentation:

import { revalidateTag } from 'next/cache'
 
export async function updateUser(id: string) {
  // Mutate data
  revalidateTag('user', 'max') // Recommended: Uses stale-while-revalidate
}

The knowledge sources indicate this is an intentional API change in Next.js 16, not a bug. You must update your revalidateTag calls to include the second argument for proper cache revalidation behavior.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

I already add revalidateTag('updated-agent-config', 'max')

but its not working @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, there are several known issues with revalidateTag not working properly in Next.js 15 and 16, particularly on Vercel deployments. Here are the documented problems and potential solutions:

Known Issues with revalidateTag on Vercel

1. Vercel-Specific Cache Behavior

From Inconsistency between Local and Vercel related to Cache Data, users report:

"On local, the behavior works as expected. The issue arise on Vercel. Doing the same reproduction described above, seems to not revalidate the Full Route Cache (try 2-3 times)."

One user found a solution:

"For those who have a similar problem, you may need to set CDN-Cache-Control and/or Vercel-CDN-Cache-Control."

There's also a reference to a Vercel community thread about this issue: https://vercel.community/t/bug-data-cache-sometimes-is-stuck/2979

2. Inconsistent Revalidation Behavior

From Inconsistent revalidate behavior when deployed to Vercel:

"Following the steps from the previous section, I expect the next request after a revalidatePath to return a X-Vercel-Cache: REVALIDATED header, along with fresh data. Instead, a X-Vercel-Cache: HIT or X-Vercel-Cache: STALE header is sometimes returned, along with stale data."

The issue notes: "I have also tested this locally using npm run build and npm run start, and it works as expected."

3. Multi-Tenant Applications

From revalidatePath with middleware doesn't work on multi-tenant project:

#

"I am running into this issue right now with a multi-tenant application. I have logged the path that middleware has as /domain/page-slug and I am successfully calling an API endpoint with revalidatePath("/domain/page-slug") with the proper domain name in there and the data does not refresh."

One user commented: "It's a shame that this hasn't been fixed after 2 years."

Potential Solutions

1. Use revalidatePath Instead

From Page not revalidated when fetch no longer returns 404:

"My conclusion: revalidateTag() does not work on 404. revalidatePath does work."

2. Check Your Next.js Version

From Vercel Deployment: Cache Revalidation Failure in Next.js:

"This issue appears to have been fixed in version 15.2.0-canary and above, at least for revalidateTag."

However, from Data Not Updating Properly in Next.js Server Actions:

"i have the same issue with revalidateTag which stopped working on nextjs v15.1.0 but was working on nextjs v15.0.4"

3. Verify Vercel Data Cache Settings

According to the Vercel Data Cache documentation:

"When the system triggers a revalidation, Vercel marks the corresponding path or cache tag as stale in every Vercel CDN region. The next request to that path or tag, regardless of the region, initiates revalidation and updates the cache globally. Vercel purges and updates the regional cache in all regions within 300ms."

Debugging Steps

#
  1. Check response headers - Look for X-Vercel-Cache headers to see if revalidation is actually happening
  2. Try revalidatePath instead of revalidateTag to see if the issue is tag-specific
  3. Consider opening a Vercel support ticket - Multiple issues suggest this may be infrastructure-related rather than a Next.js code issue

Unfortunately, the knowledge sources indicate this is a persistent issue across multiple Next.js versions when deployed to Vercel, with no definitive solution documented. The behavior difference between local and Vercel deployments suggests it may be related to Vercel's caching infrastructure rather than Next.js itself.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@weak spear Kapa.ai is still learning and improving, please let me know how I did by reacting below.

weak spear
#

is it because {next: {revalidate: 3600, tags: ["tags"]}} revalidate and tags is conflict? @civic osprey

civic ospreyBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, yes, there is a documented conflict between using revalidate and tags together in the same fetch options.

The Conflict

According to the fetch API reference:

"Good to know:

  • Conflicting options such as { revalidate: 3600, cache: 'no-store' } are not allowed, both will be ignored, and in development mode a warning will be printed to the terminal."

While this specific example mentions cache: 'no-store', the documentation indicates that conflicting cache options can cause both to be ignored.

How revalidate and tags Work Together

From the Incremental Static Regeneration examples, the documentation shows they can be used together:

const getCachedPosts = unstable_cache(
  async () => {
    return await db.select().from(posts)
  },
  ['posts'],
  { revalidate: 3600, tags: ['posts'] }
)

However, the behavior is different:

  • revalidate: 3600 - Time-based revalidation (automatic after 1 hour)
  • tags: ['posts'] - On-demand revalidation (manual via revalidateTag)

The Issue in Next.js 16

From the fetch options.next.revalidate documentation:

"Good to know:

  • If an individual fetch() request sets a revalidate number lower than the default revalidate of a route, the whole route revalidation interval will be decreased.
  • If two fetch requests with the same URL in the same route have different revalidate values, the lower value will be used."

Recommendation

#

Based on the knowledge sources, when using revalidateTag in Next.js 16, you should:

  1. Use tags without time-based revalidation if you want on-demand control:
fetch(url, {
  next: { tags: ['updated-agent-config'] }
})
  1. Or use time-based revalidation without tags if you want automatic revalidation:
fetch(url, {
  next: { revalidate: 3600 }
})

The knowledge sources suggest that mixing both strategies may cause unexpected behavior, particularly in Next.js 16 where the caching model has changed significantly. Try removing the revalidate: 3600 and only using tags to see if your revalidateTag calls start working properly.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: