Skip to content
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

Unnecessary top-level render from the router #37139

Open
1 task done
gaearon opened this issue May 23, 2022 · 5 comments · Fixed by #37431
Open
1 task done

Unnecessary top-level render from the router #37139

gaearon opened this issue May 23, 2022 · 5 comments · Fixed by #37431
Assignees
Labels
bug Issue was opened via the bug report template.

Comments

@gaearon
Copy link

gaearon commented May 23, 2022

Verify canary release

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

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.2.0: Sun Nov 28 20:28:41 PST 2021; root:xnu-8019.61.5~1/RELEASE_ARM64_T6000
Binaries:
  Node: 16.14.2
  npm: 8.5.0
  Yarn: 1.22.15
  pnpm: 6.11.0
Relevant packages:
  next: 12.1.7-canary.11
  react: 18.1.0
  react-dom: 18.1.0

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

If you have rewrites in next config:

module.exports = {
  async rewrites() {
    // ...

Then Next.js will cause a top-level re-render right after the initial navigation:

(location.search || process.env.__NEXT_HAS_REWRITES)))
) {
// update query on mount for exported pages
router.replace(

This has negative performance implications (if you don't have a bunch of memos, it may render the entire app twice 🙃 ), and can lead to cascading updates in userland effects which in turn may cause Suspense boundaries to get dehydrated (#33861 (comment)).

Expected Behavior

I would not expect Next.js to call replaceState on every single initial navigation for every page if I have defined rewrites in my config. (I only used rewrites for the RSS feed.) It is not obvious that just having rewrites in the config degrades the perf of every page in the application. In either case, even if replaceState is called, I would not expect the router component to propagate a context update if nothing in the route context actually changed (the path is the same, etc).

To Reproduce

Here's the branch:

https://github.com/gaearon/sc-bug-repro/tree/render-twice

yarn, yarn build, yarn start.

Notice that in production the effect runs twice. However, it should run once because there is no reason for the second render in production. If you remove rewrites from the config, it will run once as expected.

@gaearon gaearon added the bug Issue was opened via the bug report template. label May 23, 2022
@shuding
Copy link
Member

shuding commented May 25, 2022

Originally, we added that behavior in #24189 to support query parameters in rewrites:

{
  source: '/rewriting-to-auto-export',
  destination: '/auto-export/hello?rewrite=1',
},

...which is a valid use case. I’m wondering that if it makes sense to only trigger the router's subscription if the state doesn't change (with deep comparison):

private set(
state: typeof this.state,
data: PrivateRouteInfo,
resetScroll: { x: number; y: number } | null
): Promise<void> {
this.state = state
return this.sub(
data,
this.components['/_app'].Component as AppComponent,
resetScroll
)
}

@gurkerl83
Copy link
Contributor

This may not be directly related to rewrites, causing unnecessary re-renders at the top level, but for re-renders in general.

The linked issue describes the problem of changing the hash of a route, which per se should not result in a re-render of the entire app starting in _app. Indeed, it is a change made to the router context; it seems only a single property "asPath" changes. Re-render from the top is too expensive for inner page navigation, which should be as lightweight as possible.

Maybe the linked issue can also be addressed to solve the current problem.

#34729

@gaearon
Copy link
Author

gaearon commented May 25, 2022

Since the hash is part of the router context I don’t think it could be updated independently. But the suggestion to do a comparison first makes sense to me.

@cramforce
Copy link
Contributor

Reopening since the change was reverted.

@cramforce cramforce reopened this Jun 22, 2022
kodiakhq bot pushed a commit that referenced this issue Jul 25, 2022
Lands #37431 again, but it only solves the re-render issue completely for the middleware case (closes #38267), not the `rewrites` case (#37139).

For `rewrites`, the blocker is `isReady` always being `false` initially and to match the markup on hydration we can't simply work around it without a re-render. Need to come up with another fix.

## Bug

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
@kuus
Copy link

kuus commented Jan 16, 2023

hi, is there any update or ongoing/planned work on this?

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.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants