vercel / next.js

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

Fetch being called twice when invalidating #44655

Open patrick91 opened 1 year ago

patrick91 commented 1 year ago

Verify canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 22.2.0: Fri Nov 11 02:03:51 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T6000
Binaries:
  Node: 16.15.0
  npm: 8.5.5
  Yarn: 1.22.19
  pnpm: 7.5.0
Relevant packages:
  next: 13.1.2-canary.1
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/patrick91/next-cache-exploration

To Reproduce

Using the repo:

To run, start the backend first, you can do so by using docker:

cd backend
docker build -t next-backend-for-cache .
docker run -p 8000:8000 next-backend-for-cache

Then in another terminal:

cd frontend
pnpm i
pnpm run build
pnpm start

Now you can go to http://localhost:3000 and see the requests done to the backend service in its terminal

Describe the Bug

I've tried the following code to do a fetch request in a page in the new app dir:

export default async function Home() {
  const query = `
    query {
      country(code: "CH") {
        name
      }
    }`;

  const qs = new URLSearchParams({ query });
  // const url = `http://localhost:8000?${qs.toString()}`;
  const url = `http://localhost:8000`;

  const data = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query }),
    next: {
      revalidate: 5,
    },
  }).then((res) => res.json());

  return (
    <pre>
      <code>{JSON.stringify(data, null, 2)}</code>
    </pre>
  );
}

It seems to work well and cache things as explained in the doc, but when it tries to invalidate it calls the endpoint twice as shown in the video below:

https://user-images.githubusercontent.com/667029/211041628-8bdd4ca2-342c-43f5-8fcb-91dfec0c0b6f.mp4

I'm uncertain if this is expected. I think it should only do a call and I haven't found anything related to this behaviour.

On top of that, the request is a POST request, not sure if we intend to cache them as well šŸ¤”

Expected Behavior

The backend service should be only called once per invalidation.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

next start

fethiyildiz commented 1 year ago

This problem still exist in "13.1.6" version.

At first i thought React 18 render twice problem then i disable strict mode in next.config.js file adding reactStrictMode: false property but this didn't worked.

Also checked in production build with next start this didn't worked either.

I tested with fresh create next app template with below app/page.tsx code;


// This doesn't work either!
// export const revalidate = 15;

const getRandomProduct = async () => {
  console.log("\n*********************")
  const id = parseInt((Math.random() * 29).toString()) + 1;

  console.log(`Revalidating for ${id}!`)
  const products = await fetch(`https://dummyjson.com/products/${id}`, { next: { revalidate: 15 } });

  return products.json();
}

export default async function Home() {
  const product = await getRandomProduct();

  console.log("Title: ", product.title)
  console.log("*********************\n")

  return (
    <div style={{margin: 20}}>
      <h1>{product.title}</h1>
    </div>
  )
}

Every revalidate logs 2 output to terminal like below.

*********************
Revalidating for 27!
Title:  Flying Wooden Bird
*********************

*********************
Revalidating for 11!
Title:  perfume Oil
*********************
lhz516 commented 1 year ago

Same issue here in v13.2.4. Not only the fetch did twice, but also the page rendered twice on the server

zsrobinson commented 1 year ago

I seem to be having the same issue with v13.3.0 and revalidating using the next.revalidate option in the fetch API.

mnismi commented 1 year ago

Same here... fetching twice and rendering on the server twice next v13.4.4

KaiKloepfer commented 1 year ago

This is happening for us as well on 13.4.6. Server components render twice during next build. We have a few very expensive data fetching queries, so running all of those twice significantly increases build time.

XpTo2k commented 1 year ago

same on "next": "13.2.3"

1hemantbhatt commented 1 year ago

This still exists on:
"next": "13.4.10", Started a similar discussion:
https://github.com/vercel/next.js/discussions/52934

Randgalt commented 1 year ago

Happening for me on 13.4.19. This is a route handler via POST. Importantly, when the second request goes to the route handler the body is wrong. I can't understand where that body is coming from either.

Note: I just tested using an older pages/api API handler and it's not called twice.

QingjiaTsang commented 11 months ago

same here on 13.4.19 with app directory. In prod env, everytime when I login and router.push to the home page site where I do the SSR data fetching at layout.tsx, it'll fetch twice, but after that every refresh only causes one single fetching.

In addition, it's weird that if I set reactStrictMode: false at the next.config.js file, then it'll be settled down in dev env. I'm confused how come it goes well with that? I mean what the reactStrictMode does here with SSR?

Update: it still happens on nextjs14

matsunaga-matzlika commented 11 months ago

The same issue occurs in version 13.5.5.

Regrettably, I have implemented the following workaround.

let calledState: boolean = false;

export const Sample: React.VFC = () => {
  const [called, setCalledOrg] = useState<boolean>(calledState);

  const setCalled = (arg: boolean | ((prev: boolean) => boolean)): void => {
    calledState = (typeof arg === 'function') ? arg(calledState) : arg;
    setCalledOrg(calledState);
  };

  useEffect(() => {
    (async (): Promise<void> => {
      if (called) {
        return;
      }
      await init();
      setCalled(true);
    })();
  }, [called]);
mo-cherif commented 10 months ago

Got the same problem maybe due of the strict mode.

d-rubin commented 10 months ago

Same issue here. Implemented a similar workaround but I have an empty dependency array.

Miindaugas commented 10 months ago

Same here. On production build rendering/fetching called when using revalidation twice.

offwork commented 9 months ago

Unfortunately, everyone is alone in this matter. NextJS takes consolation by warning you that the fetch API is experimental.

d-rubin commented 9 months ago

How do you know that the fetch API from Next.js is experimental?

offwork commented 9 months ago

NextJS gives this warning at buid time

leohxj commented 8 months ago

same issue on v14.0.2

Edit by maintainers: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote šŸ‘ on the issue description or subscribe to the issue for updates. Thanks!

tomellwood-github commented 8 months ago

Nothing in the data fetching/caching docs or v14.0.4 build logs mention that fetch is still experimental. However there are discussions around this topic https://github.com/vercel/next.js/discussions/52934 relating to RSC and HTML generation.