How to Fix: `use cache` directive does not work with notFound(), shows an error instead
The failure is caused by a subtle contract mismatch in the Next.js App Router: the "use cache" directive expects a cacheable, serializable render path, but notFound() intentionally aborts rendering by throwing a special internal signal. When both are combined in the same execution path, development mode can surface that control-flow throw as an error instead of converting it into the expected 404 UI.
If you reproduced this issue from the linked repository, the symptom is usually the same: a route or server-rendered path marked with "use cache" calls logic that eventually triggers notFound(), and instead of rendering the segment’s not-found boundary, Next.js shows an error.
Understanding the Root Cause
In the App Router, notFound() does not return a normal value. Internally, it throws a framework-specific signal so Next.js can stop rendering the current segment and switch to the configured 404 handling flow. That works correctly when the framework is in a render path that understands this signal.
The problem appears when that signal is triggered from code wrapped by "use cache". Cached functions and cached component execution paths are treated differently because Next.js tries to memoize or replay the result of the computation. A thrown not-found signal is not a normal cacheable payload, and in affected canary builds the development server can treat it like an unexpected error instead of a valid routing outcome.
Technically, this is best understood as a clash between:
- Cache semantics: produce deterministic, reusable output for the same inputs.
- Routing interruption semantics: abort rendering immediately with
notFound(). - Dev-mode behavior: stricter error surfacing and incomplete handling in affected canary versions.
So the issue is not that your resource lookup is wrong. The issue is that notFound() is being raised from inside a path that Next.js is attempting to treat as cacheable work.
In practice, that means this pattern is risky:
async function getPageData(id) {
"use cache"
const data = await fetchData(id)
if (!data) {
notFound()
}
return data
}
Even though the intent is reasonable, the 404 decision happens inside cached execution. In the affected versions, that is where the error behavior comes from.
Step-by-Step Solution
The safest fix is to separate cacheable data access from routing decisions. Let cached code return a normal value such as null, then call notFound() outside the cached function, in the page or server component render flow.
1. Move notFound() out of the cached function
Refactor code so the cached layer only fetches data.
import { notFound } from "next/navigation"
async function getPageData(id) {
"use cache"
const data = await fetchData(id)
return data ?? null
}
export default async function Page() {
const data = await getPageData("home")
if (!data) {
notFound()
}
return <div>{data.title}</div>
}
This is the most reliable workaround because the cache layer stays pure, while the route layer owns navigation outcomes like 404.
2. Keep the cached function serializable and predictable
A cached function should ideally return plain data, not framework control-flow behavior.
type PageData = {
title: string
} | null
async function getPageData(slug: string): Promise<PageData> {
"use cache"
const record = await db.page.findUnique({ where: { slug } })
return record ? { title: record.title } : null
}
This makes the function easier to cache and easier to reason about across server renders.
3. Perform the 404 check in the page, layout, or route segment
Use notFound() only where Next.js is definitely expecting route-level control flow.
import { notFound } from "next/navigation"
export default async function Page({ params }) {
const data = await getPageData(params.slug)
if (data === null) {
notFound()
}
return (
<main>
<h1>{data.title}</h1>
</main>
)
}
4. If possible, update Next.js
Because the reproduction targets a canary build, verify whether the bug still exists in a newer canary or stable release. This class of issue is often fixed quickly once reported. Check the relevant framework release notes on the Next.js releases page.
If updating is not immediately possible, the refactor above is the best workaround.
5. Validate behavior in both dev and production
Some framework bugs are more visible in development mode than in production. Test both:
npm run dev
npm run build
npm run start
If development still throws but production behaves correctly, you are likely hitting a dev-only framework bug. Even then, the refactor remains a better long-term pattern.
Recommended final structure
// data.ts
export async function getCachedPage(slug: string) {
"use cache"
const page = await fetchPage(slug)
return page ?? null
}
// app/[slug]/page.tsx
import { notFound } from "next/navigation"
import { getCachedPage } from "./data"
export default async function Page({ params }) {
const page = await getCachedPage(params.slug)
if (!page) {
notFound()
}
return <article>{page.title}</article>
}
This structure cleanly separates data caching from route interruption.
Common Edge Cases
- Calling notFound() inside nested cached helpers
Even if the page itself does not contain"use cache", a deeply nested helper might. If that helper throwsnotFound(), you can still hit the same failure. Audit the full call chain. - Throwing other framework interrupts from cached code
Similar caution applies to redirect-like control flow. If a function is meant to be cached, avoid mixing it with route interruption concerns. - Returning undefined instead of null
Use a clear sentinel value.nullis better than an ambiguousundefinedwhen deciding whether to render or callnotFound(). - Mixing fetch caching with use cache
You may have both fetch-level caching and function-level caching. That can make debugging harder. Start by simplifying the data path and ensuring only one layer is responsible for lookup behavior. - No matching not-found UI file
If your route segment does not define an appropriatenot-found.tsxor rely on the default 404 behavior correctly, debugging gets confusing. Make sure the segment has proper not-found handling. - Dev/prod mismatch
Some users only see this in dev. Do not assume the issue is gone without checking production, but also do not assume production safety means the pattern is correct. The architectural fix is still recommended.
FAQ
Can I use "use cache" and notFound() together at all?
Yes, but not in the same execution responsibility. Use "use cache" for fetching or computing data, then evaluate the result in the page or layout and call notFound() there.
Why does this show up mostly in canary or development builds?
Canary releases often contain in-progress behavior, and development mode exposes framework control-flow problems more aggressively. A thrown internal not-found signal may be surfaced as an error when the cache layer does not yet handle it properly.
Should I replace notFound() with a custom 404 component return?
No. In the App Router, the correct semantic approach is still notFound(). The fix is not to avoid 404 handling, but to trigger it from the correct layer outside cached code.
The key takeaway is simple: cache data, not routing interrupts. If a lookup can miss, let the cached function return a normal value and let the route decide whether that miss should become notFound(). That pattern avoids this bug and aligns better with how Next.js expects cached execution and 404 rendering to interact.