vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.89k stars 26.67k forks source link

Request memoization sometimes doesn't work #55168

Open lesha1201 opened 1 year ago

lesha1201 commented 1 year ago

Link to the code that reproduces this issue or a replay of the bug

https://codesandbox.io/p/sandbox/distracted-bush-42yd2t?file=/app/(main)/layout.tsx:27,1-28,19

Description

For some reason, request memoization doesn't work for identical requests in the same render pass.

In the examples below, I added logs react.shared-subset.development.js to display React's current cache.

image

In the first video, we can see that request memoization works as expected when we start the application and open the page for the first time:

https://github.com/vercel/next.js/assets/10157660/2868373b-9ff9-43f4-b779-8a76ed2c8f20

In the second video, we can see that request memoization doesn't work for the next page reload:

https://github.com/vercel/next.js/assets/10157660/0cec9b16-5ae9-4d3e-b909-747488924c6d

See updates below for more context.

Current vs. Expected behavior

I expected request memoization to work for identical requests in the same render pass for layout.ts but it only worked on initial start and didn't work for the following page reloads.

Verify canary release

Provide environment information

From the example on the video:

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP Fri Jan 27 02:56:13 UTC 2023
Binaries:
  Node: 20.1.0
  npm: 9.6.4
  Yarn: 1.22.19
  pnpm: 8.1.0
Relevant Packages:
  next: 13.4.20-canary.23
  eslint-config-next: 13.4.19
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A

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

App Router, Data fetching (gS(S)P, getInitialProps)

Additional context

Update 2023-09-09 14:31

Request memoization works fine when I set runtime to Edge:

export const runtime = 'edge';

Update 2023-09-09 14:37

It seems the issue is in how Next.js loads modules.

Note that on the videos below I use Next.js v13.4.19 but I'm sure it's applicable to canary version as well.

In the first video, I set runtime to edge and we can see that it loads modules once and request memoization works properly:

https://github.com/vercel/next.js/assets/10157660/c673e91a-04a5-43a1-a611-41ec48467270

However, when the runtime is nodejs, it loads modules more than necessary (and also for some reason react-server-dom-webpack-server.edge.development.js but I'm not sure if it's wrong) and after each getData which means it overwrites React's request storage:

https://github.com/vercel/next.js/assets/10157660/e4f7a290-29fd-411c-b668-8807a334a959

saraahso commented 11 months ago

I'm interested in this topic. I've been using Node runtime and something feels odd with the requests. I'm currently not able to use edge because of a package but I will try to solve it and come back with my input.

gellezzz commented 11 months ago

I have exactly the same problem on fresh install of next.js.

image

Every first start of the server and load the page all work fine. After reload the page memoization not work.

log:

image

gokhancelikkaya commented 11 months ago

I have a simple endpoint at http://localhost:8088/test which only logs something to check if request is received. And I have the following code in my page.tsx. When I reload the page I see 4 logs in my backend server; 2 from metadata 2 from Page. Even though it is exactly the same request, it is not deduplicated as explained in NextJS documentation

When I run my application in prod mode with yarn build && yarn start, it works fine and deduplicates request. But in dev mode yarn dev I have this issue. I tried disabling reactStrictMode but that also did not help.

import { Metadata } from 'next';

export const fetchCache = 'force-no-store';

async function getItem() {
  const res = await fetch('http://localhost:8088/test');
  return res.json();
}

export async function generateMetadata(): Promise<Metadata> {
  getItem();
  getItem();
  return {
    metadataBase: new URL('localhost:3000'),
    title: 'test'
  };
}

export default async function Page() {
  getItem();
  getItem();
  return (
    <>
      <p>test</p>
    </>
  );
yarikpetrenko commented 10 months ago

Having the same issue, did someone found how to resolve this issue?

regan-karlewicz commented 10 months ago

I am seeing the same behavior as described in the previous two comments.

I have Header and Footer components in my layout.tsx, both fetch GET /api/content.

GET /api/content is a route.ts handler in my NextJS app. On the first request, I see one fetch made to this route. On subsequent requests, I see two fetches made to this route.

cbou commented 7 months ago

Same here. Request memoization seems to work using next start but not next dev.

kuki-quupi commented 5 months ago

I'm having the same issue with Next.js v 14.1.4. I'm calling the same fetch with cache:"no-store" function getData in layout and then in child page of that layout. I expect the data to be memoized in first fetch (in layout) and on second call of the fetch just to render the memoized data, but it always runs twice. This is happening in both build and dev mode.

yarikpetrenko commented 5 months ago

@kuki-quupi Why do you expect memorised requests when you clearly set cache to "no-store"?😅

Opting out of Data Caching

fetch requests are not cached if:

The cache: 'no-store' is added to fetch requests. The revalidate: 0 option is added to individual fetch requests. The fetch request is inside a Router Handler that uses the POST method. The fetch request comes after the usage of headers or cookies. The const dynamic = 'force-dynamic' route segment option is used. The fetchCache route segment option is configured to skip cache by default. The fetch request uses Authorization or Cookie headers and there's an uncached request above it in the component tree.

kuki-quupi commented 5 months ago

@kuki-quupi Why do you expect memorised requests when you clearly set cache to "no-store"?😅

Because although the data should not be cached to Data cache it should still be memoized on Router cache. Here is what docs say: "For uncached data (e.g. { cache: 'no-store' }), the result is always fetched from the data source, and memoized." Link to docs: https://nextjs.org/docs/app/building-your-application/caching#request-memoization

kuki-quupi commented 5 months ago

@kuki-quupi Why do you expect memorised requests when you clearly set cache to "no-store"?😅

Because although the data should not be cached to Data cache it should still be memoized on Router cache. Here is what docs say: "For uncached data (e.g. { cache: 'no-store' }), the result is always fetched from the data source, and memoized." Link to docs: https://nextjs.org/docs/app/building-your-application/caching#request-memoization

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. But, when i have URL /dashboard/user, and if i fetch the data in dashboard layout and page and then again the same fetch function in /user page, then data is not memoized and i get two fetch calls. Would like to hear others experience...

killer-cf commented 3 months ago

the same here with the app router. memoization only works on the first load, but not on subsequent loads. but everything works fine in production with next start

utkarshuday commented 2 weeks ago

I have faced the similar issue. It works in production but not in development mode. Is there any specific reason for it?