next.js: [NEXT-1208] Hot reloading in the app directory causes infinite rerenders

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.4.0: Mon Mar  6 20:59:28 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T6000
    Binaries:
      Node: 19.9.0
      npm: 9.6.3
      Yarn: 1.22.19
      pnpm: 8.5.1
    Relevant packages:
      next: 13.4.3-canary.1
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 4.9.4

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

App directory (appDir: true), Data fetching (gS(S)P, getInitialProps)

Link to the code that reproduces this issue

https://github.com/UgoRomi/hot-reload-rerender-bug

To Reproduce

  1. Start the project with pnpm dev
  2. navigate to localhost:3000 and open the console
  3. edit anything (for example) the console log message in page.tsx or AsyncServerComponent.tsx and save the file
  4. Go back to the webpage and you can notice the console.log statements caused by the rerendering of the components
  5. As far as I can tell this only happens when there is a fetch inside a client component that has an async server component as a child. It also happens if you fetch the data using swr

Describe the Bug

Whenever you fetch data in a Client Component that has an async Server Component as a child, hot reloading the files will cause infinite rerenders

Expected Behavior

To not have infinite rerenders

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1208

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 26
  • Comments: 22 (3 by maintainers)

Commits related to this issue

Most upvoted comments

This is still an issue in 14.0.4

We’re aware of this and exploring solutions to better handle this. In the meantime, you should use a server component for data fetching, and forward the result to a client component. This is effectively the same thing as a “data loader” like getServerSideProps. Learn more here.

I’ve just run into this same issue and boiled it down to as small an issue as I can find

For me, all you need is an empty page inside the /app folder with use client enabled

// app/test/page.tsx
"use client";

export default async function TestPage() {
  console.log("test");
  return <div className="test"> Test Page </div>;
}

Editing anything causes an infinite loop. This happens within any file that is underneath any use client component, which in development mode (especially if you’re trying to view draft content) could be any file.

It seems to be the combination of use client and async causing the problem. Switching to server components (obviously) does not render any console logs in the browser. Removing async stops the infinite loops (renders the console.log about 4 times) but also prevents data from being fetched within this component if we were to expand it.

EDIT:

The workaround I’ve found is to disable any high-level use client components (I have a preview wrapper just under my page for showing preview data page > preview-wrapper > template > etc

use client should typically exist on the ‘leaves’ of your project (the outermost components) but in certain situations it can make sense to use them closer to the center - which will cause issues until this bug is resolved.

This issue buries the lede.

This appears to be caused by client components being declared as async.

That is not a stable feature in React, yet, which is probably why it’s not well integrated with Next, and breaking. See Sophie’s tweet below, for more context.

Removing the async from the client component fixed it (it was never even meant to be async, and I wasn’t awaiting anything - but just returning a Promise from a client component appears to be enough to wreck havoc on Next)

image

https://twitter.com/sophiebits/status/1647775289790050304

How often do the infinite rerenders occur for you guys? Mine is reloading server components once pr. second 🤔 image

@TommySorensen - try the latest canary version - #52061 was released as part of v13.4.9-canary.2.

I do still see infinite refreshing in next dev with my dynamic routes inside of parallel routes with that version though, so I may have to create a minimal repro for it.

Update: created a minimal repro @ https://github.com/tnoor-co/next-infinite-parallel-refresh and I’ll continue to the conversation in PR linked.

@TommySorensen please ensure you are not using an async component that’s marked with the "use client" directive. We just landed an ESLint rule for this as well.

Me too, infinite re-render into parallel routes still exists here

@TommySorensen In my initial reproduction I didn’t have any async client components either. I had an async server component which was imported inside of a client component that fetched some data. Maybe you have something similar? If so you could try doing the data fetching in a server component, and then wrap your client component with it passing props down (Learn more here)