Thanks to visit codestin.com
Credit goes to github.com

Skip to content

next/image (including future) doesn't provide a way to opt out of rerenders via memo when using useRouter #40074

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

Open
1 task done
davidbarker opened this issue Aug 30, 2022 · 2 comments
Labels
bug Issue was opened via the bug report template. stale The issue has not seen recent activity.

Comments

@davidbarker
Copy link
Contributor

davidbarker commented Aug 30, 2022

Verify canary release

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

Provide environment information

Operating System:
      Platform: darwin
      Arch: x64
      Version: Darwin Kernel Version 21.6.0: Wed Aug 10 14:25:27 PDT 2022; root:xnu-8020.141.5~2/RELEASE_X86_64
Binaries:
      Node: 16.16.0
      npm: 8.11.0
      Yarn: 1.22.18
      pnpm: N/A
Relevant packages:
      next: 12.2.5
      eslint-config-next: 12.2.3
      react: 18.2.0
      react-dom: 18.2.0

warn  - Latest canary version not detected, detected: "12.2.5", newest: "12.2.6-canary.6".
        Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
        Read more - https://nextjs.org/docs/messages/opening-an-issue

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

I have a page with hundreds of table cells and an image in each of those table cells (so my page contains hundreds of images). When an image is selected, a sidebar opens with details, and I replace the URL in the location bar using useRouter.

I've been having performance issues when selecting an image — the interaction would take 500ms+, so I've been going through my code to optimize everything.

I'm able to optimize everything via memoization except for the next/image component (I'm actually using next/future/image but I've checked with both).

When I replace/push a new query string using useRouter, the whole tree rerenders. This is fine for most of my components because of my memoization. However, every Image component always rerenders (according to the React dev tools because of a context change). I've tried to wrap the Image component with useMemo but no matter what I try, the Image component rerenders.

If I replace the Next Image component with a plain img, my memoization is perfect and those img components do not rerender. However, I really want to use the Image component because of all the features it offers.

Is there any way to stop the Image component rerendering when the context/router changes? Am I missing something?

If there isn't a way currently, perhaps this is a bug? It seems quite important to have control over when the component rerenders when using useRouter or making a change in the context.

Update

Looking at the ranked chart in the profiler, it seems it may be ImageConfigContext and/or RouterContext causing these rerenders. As mentioned above, I can stop my own components rerendering by memoizing them, but that doesn't seem possible with the Next Image component.


import Image from 'next/future/image';
import { memo, useMemo } from 'react';

import { styled } from '~/theme/stitches.config';

// ...

function ImageTile({
  alt,
  height,
  isSelected,
  onClick,
  src,
  width,
}: {
  alt: string;
  height: number;
  isSelected?: boolean;
  onClick: () => void;
  src: string;
  width: number;
}) {
  return (
    <Button onClick={onClick}>
      <Image alt={alt} height={height} src={src} width={width} />
    </Button>
  );

}

export default memo(ImageTile);

I have also tried wrapping the Image element in useMemo like below but it still rerenders due to a context change.

// ...

  const imageEl = useMemo(() => (
    <Image alt={alt} height={height} src={src} width={width} />
  ), [alt, height, src, width]);
  
  return (
    <Button onClick={onClick}>
      {imageEl}
    </Button>
  );

// ...

Expected Behavior

The Next Image component should provide a way to prevent rerender during a context change (like useRouter changing).

Link to reproduction

https://codesandbox.io/s/gifted-hermann-ygwj5u?file=/components/MyTile.tsx

To Reproduce

Using the regular Next Image component (non-memoized) (line 12 of MyTile.tsx), the render takes 547ms:

Next Image component render

Using the memoedImage component (line 13 of MyTile.tsx), the render takes 684ms:

Memoized Next Image component render

Using the native img component (line 14 of MyTile.tsx), the render takes 43ms (~12× faster):

Native img component render

@davidbarker davidbarker added the bug Issue was opened via the bug report template. label Aug 30, 2022
@hedgepigdaniel
Copy link

I think this is a more general issue - The (only?) way to navigate is by using the NextRouter from the underlying react context, from useRouter:

export function useRouter(): NextRouter {

useRouter returns the routing state, so obviously it has to update when a routing action happens. But the push etc methods should not similarly need to update.

The problem is that both the state and the methods for changing it are lumped into the same React context.

@nextjs-bot
Copy link
Collaborator

This issue has been automatically marked as stale due to two years of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you.

@nextjs-bot nextjs-bot added the stale The issue has not seen recent activity. label May 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template. stale The issue has not seen recent activity.
Projects
None yet
Development

No branches or pull requests

3 participants