nextjs - ✅(Solved) Fix `use cache: private` not working during client-side navigation [1 pull requests, 18 comments, 12 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#85672Fetched 2026-04-08 02:14:33
View on GitHub
Comments
18
Participants
12
Timeline
51
Reactions
25
Timeline (top)
subscribed ×24commented ×18mentioned ×3cross-referenced ×2

Fix Action

Fixed

PR fix notes

PR #118: AIEW-152 Caching by fetch force-cache

Description (problem / solution / changelog)

JIRA Task 🔖

  • Ticket: AIEW-152
  • Branch: feature/AIEW-152

작업 내용 📌

privateFetch에 force-cache 속성 추가


주요 변경 사항 ✍️

privateFetch에 force-cache 속성 추가

  • 기존에는 페이지에 들어갈 때마다 fetch를 진행했었음.
  • intervew의 생성과 수정, 삭제, 그리고 report의 삭제의 경우만 데이터가 변경되기에 이 때를 제외하곤 기존 data와 달리지지 않는다고 판단함.
  • 즉 기본적으로 fetch에 대해 caching을 하는 것이 유리하다고 판단해 모든 fetch에 force-cache 도입

_lib/api.ts 파일에 각 도메인에서 사용하는 모든 api 함수를 작성함.

  • 이를 통해 유지보수 쉬운 코드 작성
  • 각 함수 scope 안에서 환경변수를 호출해야함.
    • 그렇지 않으면 build시에만 환경변수를 호출하는데, 이럴 경우 undefined값이 들어가게 됨.

not-found.tsx 도입

  • fetch 반환값이 404일 경우 next에서 제공하는 notFount()함수를 호출
  • 이 함수가 호출되면 not-found.tsx 파일이 화면에 나타남

pnpm start 생성

  • dev 에선 캐싱이 명확하게 안되는 경우가 존재함.
  • 이에 next start를 호출할 수 있는 pnpm start 생성

캐싱 성능 개선 결과

  • 평균 93% 이상의 로딩 시간 단축
  • Next.js RSC Cache + Tag 기반 invalidate 전략으로 전반적인 서버 부하 감소
  • 첫 방문 속도뿐 아니라 전환 속도 및 전체 사용자 경험 크게 향상
페이지BeforeAfter개선 비율
Dashboard1018ms158ms84.5% 개선
Interview1680ms65ms96.1% 개선
Interview / Waiting / [sessionId]1386ms43ms96.9% 개선
Reports1497ms52ms96.5% 개선
Report Detail934ms69ms92.6% 개선

dashboard GET /dashboard 200 in 1018ms (compile: 371ms, proxy.ts: 7ms, render: 641ms) -> GET /dashboard 200 in 158ms (compile: 7ms, proxy.ts: 5ms, render: 146ms)

interview GET /interview 200 in 1680ms (compile: 995ms, proxy.ts: 9ms, render: 676ms) -> GET /dashboard 200 in 65ms (compile: 7ms, proxy.ts: 7ms, render: 51ms)

interview/[sessionId] GET /interview/waiting/[sessionId] 200 in 1386ms (compile: 667ms, proxy.ts: 5ms, render: 715ms) -> GET /interview/waiting/[sessionId] 200 in 43ms (compile: 14ms, proxy.ts: 5ms, render: 24ms)

report GET /reports 200 in 1497ms (compile: 864ms, proxy.ts: 6ms, render: 627ms) -> GET /reports 200 in 52ms (compile: 7ms, proxy.ts: 7ms, render: 38ms)

report/[reportId] GET /reports/[reportId] 200 in 934ms (compile: 505ms, proxy.ts: 6ms, render: 422ms) -> GET /reports/[reportId] 200 in 69ms (compile: 13ms, proxy.ts: 5ms, render: 51ms)


테스트 방법 🧑🏻‍🔬

  • pnpm dev 실행 후 테스트
  • 캐싱 잘 되는지 확인
  • 상세 interview page에서 없는 Id 제공시 not-found 잘 뜨는지 확인
  • 상세 report page에서 없는 Id 제공시 not-found 잘 뜨는지 확인

참고 사항 📂

  • next16의 cache components의 use cache: private을 이용해 캐싱을 하고싶었지만 문제가 있어 fetch의 caching을 이용했습니다. https://github.com/vercel/next.js/issues/85672
  • 다음 pr에는 인터뷰 생성, 수정, 삭제, 리포트 삭제 될 경우 caching 된 값을 지우는 작업을 할 예정입니다.

Changed files

  • apps/web-client/src/app/(main)/_components/Header.tsx (modified, +6/-1)
  • apps/web-client/src/app/(main)/_components/MainLink.tsx (modified, +1/-6)
  • apps/web-client/src/app/(main)/dashboard/_components/Header.tsx (modified, +2/-2)
  • apps/web-client/src/app/(main)/dashboard/_components/UserInfos.tsx (modified, +2/-2)
  • apps/web-client/src/app/(main)/dashboard/_components/graph/CompanyGraph.tsx (modified, +2/-8)
  • apps/web-client/src/app/(main)/dashboard/_components/graph/RecentGraph.tsx (modified, +2/-7)
  • apps/web-client/src/app/(main)/dashboard/_components/recent/RecentInterview.tsx (modified, +2/-2)
  • apps/web-client/src/app/(main)/dashboard/_components/recent/RecentReports.tsx (modified, +2/-2)
  • apps/web-client/src/app/(main)/dashboard/_lib/api.ts (added, +42/-0)
  • apps/web-client/src/app/(main)/dashboard/page.tsx (modified, +0/-10)
  • apps/web-client/src/app/(main)/interview/(socket)/[sessionId]/page.tsx (modified, +4/-12)
  • apps/web-client/src/app/(main)/interview/(socket)/waiting/[sessionId]/components/InterviewInfo.tsx (modified, +3/-7)
  • apps/web-client/src/app/(main)/interview/_lib/api.ts (added, +40/-0)
  • apps/web-client/src/app/(main)/interview/create/[[...sessionId]]/page.tsx (modified, +2/-11)
  • apps/web-client/src/app/(main)/interview/not-found.tsx (added, +15/-0)
  • apps/web-client/src/app/(main)/interview/page.tsx (modified, +2/-7)
  • apps/web-client/src/app/(main)/reports/[reportId]/getQuestions.ts (removed, +0/-15)
  • apps/web-client/src/app/(main)/reports/[reportId]/page.tsx (modified, +2/-7)
  • apps/web-client/src/app/(main)/reports/[reportId]/questions/page.tsx (modified, +1/-1)
  • apps/web-client/src/app/(main)/reports/_components/header/ReportHeader.tsx (modified, +1/-1)
  • apps/web-client/src/app/(main)/reports/_components/header/ReportInfos.tsx (modified, +2/-10)
  • apps/web-client/src/app/(main)/reports/_components/questionsPannel/QuestionsPannel.tsx (modified, +1/-1)
  • apps/web-client/src/app/(main)/reports/_components/table/ReportTableBody.tsx (modified, +2/-9)
  • apps/web-client/src/app/(main)/reports/_lib/api.ts (added, +89/-0)
  • apps/web-client/src/app/(main)/reports/not-found.tsx (added, +15/-0)
  • apps/web-client/src/app/(main)/reports/page.tsx (modified, +2/-11)
  • apps/web-client/src/constants/cacheTags.ts (added, +7/-0)
  • package.json (modified, +1/-0)

Code Example

// 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,
        }
    }

---

// 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>
        );
    }

---

import type { NextConfig } from "next";

    const nextConfig: NextConfig = {
    cacheComponents: true,
    };

    export default nextConfig;

---

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
RAW_BUFFERClick to expand / collapse

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

extent analysis

TL;DR

The issue can be resolved by adjusting the caching strategy to account for the interaction between Router Cache and function-level caching during client-side navigation.

Guidance

  • Review the documentation for "use cache: private" and cookies() API to ensure correct usage and compatibility with Next.js version 16.0.2-canary.3.
  • Investigate the Router Cache configuration to determine if it's interfering with the function-level caching, and consider adjusting the cache settings to achieve the desired behavior.
  • Verify that the cacheTag and cacheLife functions are correctly implemented and not causing the cache to be invalidated prematurely.
  • Test the application with a different caching strategy, such as using a public cache or adjusting the stale time, to isolate the issue.

Example

No code example is provided as the issue seems to be related to the configuration and interaction between different caching mechanisms.

Notes

The provided information suggests that the issue is specific to the interaction between Router Cache and function-level caching during client-side navigation. The solution may require adjustments to the caching configuration or the implementation of the getUser function.

Recommendation

Apply a workaround by adjusting the caching strategy to account for the interaction between Router Cache and function-level caching during client-side navigation, as the root cause of the issue is not entirely clear and may require further investigation.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING