next.js icon indicating copy to clipboard operation
next.js copied to clipboard

`use cache: private` not working during client-side navigation

Open ajaykarthikr opened this issue 1 month ago • 12 comments

Link to the code that reproduces this issue

https://github.com/ajaykarthikr/next-16-use-cache-private-bug

To Reproduce

  1. Create a cached function with "use cache: private" that uses cookies():

    // lib/user.ts
    import { cookies } from "next/headers";
    import { cacheTag, cacheLife } from "next/cache";
    
    export async function getUser() {
        "use cache: private";
        cacheTag(`userdata`);
        cacheLife({ stale: 30 });
    
        const sessionId = (await cookies()).get('session-id')?.value || 'guest'
    
        console.log("Fetching user data"); // This logs on every navigation
        await new Promise((resolve) => setTimeout(resolve, 5000));
        const timestamp = new Date().toISOString();
    
        return {
            user: {
                name: "Ajay",
                email: "[email protected]"
            },
            timestamp: timestamp,
        }
    }
    
  2. Use this function in a Server Component page:

    // app/about/page.tsx
    import { Suspense } from "react";
    import { getUser } from "../../lib/user";
    
    export default async function Page() {
        return (
            <div>
                <h1>About Page</h1>
                <Suspense fallback={<p>Loading...</p>}>
                    <Content />
                </Suspense>
            </div>
        )
    }
    
    async function Content() {
        const data = await getUser();
        return (
            <div>
                <h1>{data.user.name}</h1>
                <p>Fetched at: {data.timestamp}</p>
            </div>
        );
    }
    
  3. Configure next.config.ts:

    import type { NextConfig } from "next";
    
    const nextConfig: NextConfig = {
    cacheComponents: true,
    };
    
    export default nextConfig;
    
  4. Navigate to the page (e.g., /about), then navigate away, then navigate back using client-side navigation (using <Link> component)

Current vs. Expected behavior

Expected: The cached function should return the cached result during the 30-second stale period, showing the same timestamp and not logging "Fetching user data" on subsequent navigations.

Actual: The function re-executes on every client-side navigation, showing a new timestamp each time and logging "Fetching user data" in the console.

Demo: https://next-16-use-cache-private-bug.vercel.app/

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.0.0: Wed Sep 17 21:42:08 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T8132
  Available memory (MB): 24576
  Available CPU cores: 10
Binaries:
  Node: 22.20.0
  npm: 10.9.3
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 16.0.2-canary.3 // Latest available version is detected (16.0.2-canary.3).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Use Cache

Which stage(s) are affected? (Select all that apply)

next start (local), Vercel (Deployed)

Additional context

  • The "use cache: private" directive is supposed to work with cookies() API according to the documentation
  • This appears to be related to Router Cache interaction with function-level caching during client-side navigation
  • The 2-second artificial delay in the reproduction makes it easy to observe when the function re-executes

ajaykarthikr avatar Nov 01 '25 16:11 ajaykarthikr

Hey @ajaykarthikr , This looks interesting, I'd love to take a closer look and work on a fix or reproduction enhancement. Could you please assign this issue to me if possible? Thanks for the clear repro steps

DeveloperViraj avatar Nov 01 '25 16:11 DeveloperViraj

Im also having a very similar issue, just to add onto this, when running in dev, the whole page hangs until the component that uses private cache has finished rendering. When running a build the component is streamed in but the result never cached.

alfiemartin avatar Nov 03 '25 09:11 alfiemartin

The same reason brought me here. The behaviour isn't what's documented. I had to include the old next: {revalidate} to get caching

GeniusHawlah avatar Nov 03 '25 18:11 GeniusHawlah

I wish the docs were clearer, but nothing is cached client side unless it's prefetched. (Unless you alter experimental.staleTimes.dynamic to be non-zero)

By default anything static or marked 'use cache' is prerendered and prefetched. But not 'use cache: private'. So we need to set prefetch={true} on link (as opposed to 'auto'/null), or call router.prefetch.

I think the plan is that in future we can configure 'runtime prefetching' for a page, which means that content marked 'use cache: private' will also be prefetched and cached by default. But that's not yet documented/recommended.

mdj-uk avatar Nov 03 '25 21:11 mdj-uk

It also doesn't work. I used "use cache: private" for queries with searchParams, but I can see from the server logs that the identical query is repeated, creating a new request to the server each time. If you use "use cache", then everything is ok, queries are cached and not duplicated.

However, according to the documentation, you should use "use cache: private" if you're using searchParams.

I don’t use Next.js API routes. All API requests go to an external Fastify backend.

aprinciple avatar Nov 17 '25 12:11 aprinciple

I encountered the same problem; "use cache:private" doesn't work at all.

DanhezCode avatar Nov 20 '25 00:11 DanhezCode

I have this same issue. use cache: private doesn't work at all for men and like someone mentioned as well, it increased loadtimes a lot.

MalteNord avatar Nov 24 '25 08:11 MalteNord

I have this same issue. use cache: private doesn't work at all for men and like someone mentioned as well, it increased loadtimes a lot.

Use next: { revalidate: 3600 } temporarily until they fix it.

It's strange that such a critical bug hasn't been fixed yet.

aprinciple avatar Nov 24 '25 08:11 aprinciple

same here.

@aprinciple Do you mean put next: { revalidate: 3600 } in "next.config.ts"?

lilyreadmoo avatar Dec 01 '25 06:12 lilyreadmoo

same here.

@aprinciple Do you mean put next: { revalidate: 3600 } in "next.config.ts"?

No, you have to specify this in the server request, for example.

export default async function Page() {
  const data = await fetch('https://...', { next: { revalidate: 3600 } })
}

aprinciple avatar Dec 01 '25 15:12 aprinciple

I had the same issue, but now I moved with "use catch" passing the user ID as props so each cache will be used by its own user.

cent-dxv avatar Dec 10 '25 20:12 cent-dxv

I think it's just better to use the old caching system (next: {}) instead of this. use cache wasn't designed for dynamism so it may have an unknown effect especially security wise in production.

I had the same issue, but now I moved with "use catch" passing the user ID as props so each cache will be used by its own user.

GeniusHawlah avatar Dec 10 '25 23:12 GeniusHawlah