vercel / vercel/next.js

Link component leads to unnecessary re-renders with legacyBehavior

Open
#68,596 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug Linking and Navigating Performance Runtime
Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/hardcore-neumann-dw8484

To Reproduce

Click the "Update Time" button, which updates a query parameter in the URL to the current time. Observe that the component "MyButton" re-renders due to the "Link" wrapper after a query param changed, despite its render being totally independent to changing query parameters and using React.memo

Current vs. Expected behavior

Current behavior is that the Link component uses useRouter() causing it to re-render when any query parameters change. This causes it to pass a new instance of the 'onClick', 'onMouseEnter', and 'onTouchStart' props to the child, which forces it to rerender.

Expected behavior is that the Link component should not need to re-render at all in this case, and even if it did re-render it should pass a stable function for onClick/onMouseEnter/onTouchStart to prevent the button from re-rendering (since only query params have changed, nothing related to the button's navigation has changed).

This causes many unnecessary re-renders across a website when navigations happen, in particular for components which remain mounted such as a header/left-rail or when only query parameters are changing (which doesn't impact most of the page typically).

Provide environment information
Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Sun Aug  6 20:05:33 UTC 2023
  Available memory (MB): 4102
  Available CPU cores: 2
Binaries:
  Node: 20.9.0
  npm: 9.8.1
  Yarn: 1.22.19
  pnpm: 8.10.2
Relevant Packages:
  next: 15.0.0-canary.104 // Latest available version is detected (15.0.0-canary.104).
  eslint-config-next: N/A
  react: 19.0.0-rc-06d0b89e-20240801
  react-dom: 19.0.0-rc-06d0b89e-20240801
  typescript: 5.3.3
Next.js Config:
  output: N/A
Which area(s) are affected? (Select all that apply)

Navigation, Performance, Runtime

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

next dev (local), next build (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

I started a related discussion at https://github.com/vercel/next.js/discussions/67520, but there was no traction there.

Also note that legacyBehavior is required for any custom button components (ex. using MUI or styled-components) and recommended in docs, so I don't think its valid to ignore

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked CodeSandbox and reproduce the update caused by changing the query parameter, then inspect the Link component's legacyBehavior path. Compare MyButton's renders and the onClick, onMouseEnter, and onTouchStart props before and after navigation. Done means query-parameter changes no longer cause this independent memoized child to re-render unnecessarily.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nextjs, react
Domain
frontend, performance, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.