-
Notifications
You must be signed in to change notification settings - Fork 27.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
App router deduping not working as expected #52126
Comments
Any update/quick fixes for this issue? I am experiencing a very similar thing |
This is still happening on 13.4.16 btw, and is causing large performance and costs for people not aware it is happening. @zwarunek We got around this issue by manually wrapping every fetch with the cache function documentation. |
Isn't this a massive issue? I'm kinda confused how everyone is experiencing this and nobody seems to be talking about the fact that deduping doesn't really work... |
This comment has been minimized.
This comment has been minimized.
Yes a bit annoying. We're caching the promises from the fetch in a map keyed by url and purging regularly.. Luckily not a huge throughput, so not too much of an issue. |
Has this been resolved? I tried to reproduce the issue, but it appears that the server request is only occurring once in reality. |
It is not fixed. You can check my example and view the server logs. There you will see multiple api calls to the server |
Hello, @richie-south c. May i handle this issue? Please assign to me. I'll create PR. |
@rjsdnql123 The issue is open for anyone to fix if they have the time :) |
next.js/packages/next/src/server/lib/patch-fetch.ts Lines 501 to 504 in 218d070
Hi @ztanner ! (author of incremental-cache at #53030) I'm trying to solve this issue with @rjsdnql123 . On next.js/packages/next/src/server/lib/incremental-cache/index.ts Lines 241 to 252 in 218d070
On above code, I think |
Having the same issue. Did anyone found workaround or something? From what I'm seeing wrapping fetch in cache may be the possible workaround... After a lot testing I no longer understand when it breaks and how it works. Somehow my project works fine on Next.js 13.5.4 and below, and after uploading to Vercel it stays the same, but all versions above just don't dedupe fetch requests. Most frustrating thing that when I do all the same on codesandbox it just didn't work whatever version I choose request are not getting deduped, is so unpredicted and buggy. Because of that I can't upgrade to Next 14. I'm surprised how little people care about it, there is only few issues and they don't have any responses. How people even use Next then. It completely breaks app router paradigm with many small server component where you can fetch your data and don't worry about duping. In my case I have strict rate limit which I can't break and with this bug it just break all completely. It should be fixed ASAP. I reproduces this issue on CodeSandbox for anybody who don't want to clone git repository to test this issue. Link to the code that reproduces this issue or a replay of the bughttps://codesandbox.io/p/sandbox/gallant-tree-4yxvwz To Reproduce
Expected Behavior
|
You can wrap the API requests with React.cache instead. https://react.dev/reference/react/cache
I tried this on your codesandbox and it returns the same timestamp twice, and only sends the API request once, as you expected. |
@psikoi Thank you for your replay. I just tested your suggestion and it is indeed working🔥🔥. For other people for more information you can check link that @psikoi provided and also Next docks - here But I still can't comprehend how Next dev team or someone else still didn't fixed it, If I remember correctly they said that under the hood it takes advantage of react cache and it is not clear why it's not working. |
Folks just for my curiosity for the ones who having an issue with this, are you using some fetch libraries? So I've tried a few things and it was OK for specific cases, but I've used pure fetch here. I want to check if this could be somehow related to bad references |
Docs says that dedupe works only if you use native fetch which Next.js extends. So yeah I'm personally experiencing things with pure fetch and didn't expect the same from libraries. And if you want to get this with third-party libraries it is recommended to use |
I'm seeing this specifically when using |
Just in case, this is not an issue with prefetching it's pretty much a workaround |
Just to chime in, experiencing the same here. Currently wrapped a load of our fetch functions in Umming and ahhing whether to pivot to React Query to handle some of the caching/memoization it provides without the Next's fetch faff. Currently using it for client-side requests, but could look to use it for prefteching/SSR too. My experience with React Query has been far more positive (and the devtools/visualising side) than Next's fetch, and made me feel far more confident in know what's cached/deduped etc |
@cpotey In my case |
That's a good point. I'll try and specifically define the cookie I require (currently passing through all of them like I'm seeing same things in dev/prod. I've got a console.log within my cache fetch requests, which is logging if the request is fetching fresh, rather than from cache - so can tell what's not deduping currently |
14.0.4 This problem still exists |
Hi, I am struggling with the same problem, this is a huge issue since the project uses a CMS Cloud with limits on API requests. Some not deduped fetches in layout happen like 10 times when combined with preloading links in view (the default behaviour) on a SINGLE page load. I am using the export const cmsClient = new GraphQLClient(`${BASE_URL}/graphql`, {
fetch: cache(async (input: RequestInfo | URL, params?: RequestInit) =>
fetch(input, { ...params, next: { revalidate: REVALIDATE_TIME } })
),
}) Has anyone found a fix for this or should I try downgrading to a lower version? Could the library be a problem? I am passing it the fetch function from Next.js. Edit: Now that I am looking at it, this might be a problem with the data cache and not request memoization, where the preloading of links triggers different server requests, and the memoization is not applicable. From the React docs: |
I cannot reproduce this issue anymore with next 14.1.0 and my example project. Would be great if someone else wants to try, otherwise i will close this in the future. 😃 |
I have multiple RSCs wrapped with suspense, and the BE is still getting hit multiple times -- no de-duping is occurring. Moreover, there seems to be a cap on the number of parallel requests. Per the simple logging below, you'll note the last two server component fetches take 10+ seconds (this is visible in the devTools waterfall below), however the server itself responded quickly to these last two requests, but only received the requests 10+ after Next began rendering. What is happening here?
NextJS (FE)
NestJS (BE)
|
I'm having the same issue with Next.js v 14.1.4. |
I found out that memoization works in case when you have the same level components. For example, if i have an URL /dashboard and for that URL im using layout and page.tsx, and if i call the same fetch function in both of these then that fetch function is memoized. |
I think you expectation was misaligned with what this memoization is. For the data to be "memoized" across two different page requests, it would need to be cached. The description of this utility says:
However, the type of "deduping" or "memoization" discussed in this thread only exists for the duration of a single request, meaning if for the same page load you were fetching the same data 3 times (once in layout, once in page and maybe once in generateMetadata), this memoization would make sure this fetch only happened once. This can be done using React's I understand this can be confusing as both functions have the same name, but they have different purposes, I like to thing of React's cache function as memoization, rather than an actual cache. Hopefully this can help others, and hopefully I didn't get the details wrong here, I am also learning this as I go. |
Well it might be that docs are not correct. Because it has sense that data is being memoized only during the rendering process of the single page. But docs are telling us that fetch function should be memoized through multiple layouts and pages, which would mean that data would be memoized on different URLs... Check out the diagram from the docs. It is showing 2 layers of depth, so 2 different pages should be rendered, and still fetch request should be memoized. It is either they should fix the fetch memoization or align docs with the true nature of fetch memoization. |
The docs say requests are memoized only when the URL and options are identical. |
Yes, I think the docs are very clear, it's not meant to de-dup across different URLs. The problem is that it doesn't actually work. Posting another repo with 14.1.4, the problem is still there: |
This comment was marked as off-topic.
This comment was marked as off-topic.
I'm using the How can I solve this? Current implementation: export async function generateMetadata({
params: { locale, site, slug },
}: GeneralSlugPageProps) {
const config = await getSiteConfig({ site, locale });
const page = await getBySlug({
locale,
site,
slug: slug ? slug.join("/") : "/",
});
return siteMetadata({ ... });
}
export default async function GeneralSlugPage({
params: { site, locale, slug },
}: GeneralSlugPageProps) {
const { header, footer } = await getLayout({
site,
locale,
});
const page = await getBySlug({
locale,
site,
slug: slug ? slug.join("/") : "/",
});
return (..)
}; I've tried creating a separate cached function in this component and call export const getCachedPageBySlug = cache(
async ({ locale, site, slug }) =>
await getBySlug({
locale,
site,
slug: `tickets/${slug.join("/")}`,
})
); Afterwards I reverted this change and moved the cache inside the e.g. export const getBySlug = cache(async<T = Record<string, any>>({
site,
slug,
locale = "nl",
params,
}: GetBySlugProps): Promise<StoryblokStory<T>> => {
try {
const story = await storyblokClient.getBySlug({
site,
slug: `page/${slug || ""}`,
locale,
params,
});
return story.data.story;
} catch (error) {
if (error.message === "Not Found") {
return notFound();
}
throw error;
}
}; But again the results do not change. |
@rnnyrk You are passing a new object to the cached function So you could for example simplify the function so that the arguments are only primitives (like the slug and the locale)
Or you could maybe stringify the input object, pass it as a string to the cached function and parse it inside, but that seems like a dirty hack. Not sure if there are other ways to guarantee referential equality between the page and the generate metadata function in your case when the input values are dynamic. If anyone knows, I would be interested too. |
@michalcizek Thanks, the solution in the end was indeed to use params directly instead of passing an object to cache functions. |
Maybe, this PR patch the issue |
…8535) The first iteration of `deleteAppClientCache()` was introduced in #41510. Its purpose was to remove stale client components (for SSR) from React's cache. Since React didn't (and still doesn't) offer an API for that, this was accomplished by deleting `react-server-dom-webpack` from the require cache. With the bundling that was introduced in #55362, `deleteAppClientCache()` was changed to delete the whole server runtimes (e.g. `app-page.runtime.dev.js`) from the require cache. This has the unfortunate side effect that also React's other module scope cache (back then `ReactCurrentCache`, now `ReactSharedInternals.A`) is reset between compilations. As a result, when fetching data from a route handler in a page, the fetch cache didn't work properly. This issue was masked though by response buffering for the static generation cache. In #68447 the response buffering will be removed which [uncovered the issue](https://github.com/vercel/next.js/actions/runs/10217197012/job/28270497496). React had two module-scoped caches for client components: - `chunkCache` – caches the loaded JS chunks, as specified in the SSR manifest - `asyncModuleCache` – caches the required modules, if marked as `async` in the SSR manifest The `asyncModuleCache` was subsequently dropped in facebook/react#26985. In addition, with Webpack, we don't create any client component chunks for SSR (removed from the manifest in #50959). So there's no need anymore to delete server runtime bundles from the require cache, and we can remove `deleteAppClientCache()` from the `NextJsRequireCacheHotReloader`. For Turbopack, the exported `deleteAppClientCache` function is still used because Turbopack does create SSR client component chunks. Ideally, React would offer an API to clear the chunk cache. But for now we can keep this logic, since Turbopack also handles this a bit more sophisticated than the Webpack plugin, so that the aforementioned fetch issue does not occur there. This PR in conjunction with #68193 should then also fix the issue that was reported in #52126.
Verify canary release
Provide environment information
Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 22.3.0: Mon Jan 30 20:42:11 PST 2023; root:xnu-8792.81.3~2/RELEASE_X86_64 Binaries: Node: 16.13.0 npm: 9.6.4 Yarn: N/A pnpm: N/A Relevant packages: next: 13.4.7 eslint-config-next: 13.4.7 react: 18.2.0 react-dom: 18.2.0 typescript: 5.1.6
Which area(s) of Next.js are affected? (leave empty if unsure)
No response
Link to the code that reproduces this issue or a replay of the bug
https://github.com/richie-south/nextjs-cache
To Reproduce
Clone the provided repo, run commands, check terminal logs for both api and next.
npm run api
npm run dev
Describe the Bug
Next will make multiple fetch requests to the same path on page reload even if docs says they should be deduped.
This happens on dev and production deployment.
if i reload the root page
/
i can see in my api logs that it will makes 3 request to my api, having fetch in my RootLayout and generateMetadata.If i make a reload on other pages, ex
/todo
in my provided example, i will get 5 api requests for the same path.Expected Behavior
I expect the requests to be deduped as illustrated in the docs. So instead of up to 5 requests it should be 1 when no cache is available.
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
Other platform
NEXT-1407
The text was updated successfully, but these errors were encountered: