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

Using rewrites in next.config.js causes double renders #33028

Closed
valin4tor opened this issue Jan 5, 2022 · 8 comments
Closed

Using rewrites in next.config.js causes double renders #33028

valin4tor opened this issue Jan 5, 2022 · 8 comments
Assignees

Comments

@valin4tor
Copy link

valin4tor commented Jan 5, 2022

Run next info (available from version 12.0.8 and up)

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.2.0: Sun Nov 28 20:29:10 PST 2021; root:xnu-8019.61.5~1/RELEASE_ARM64_T8101
Binaries:
  Node: 16.13.1
  npm: 8.3.0
  Yarn: 1.22.17
  pnpm: 6.11.0
Relevant packages:
  next: 12.0.8-canary.17
  react: 17.0.2
  react-dom: 17.0.2

What version of Next.js are you using?

12.0.7 and 12.0.8-canary.17 both reproduce

What version of Node.js are you using?

16.13.1

What browser are you using?

Chrome and Safari both reproduce

What operating system are you using?

macOS 12.1 (21C52)

How are you deploying your application?

Vercel, next dev and next start all reproduce

Describe the Bug

When a rewrites function is specified in next.config.js, every page renders twice in the browser, regardless of the nature of this function.

Expected Behavior

Specifying a rewrites function does not cause double renders.

To Reproduce

  1. Clone https://github.com/valerie-makes/nextjs-rewrites-bug
  2. Install dependencies: npm install
  3. Build and start the app: npm run build && npm start
  4. Visit the index page and observe the browser console:
    rendering Home
    rendering Home
    
  5. Comment out the rewrites function in next.config.js
  6. Repeat Step 3 and Step 4:
    rendering Home
    
@valin4tor valin4tor added the bug Issue was opened via the bug report template. label Jan 5, 2022
@valin4tor valin4tor changed the title Using rewrites in next.config.js causes a double render Using rewrites in next.config.js causes every page to render twice Jan 5, 2022
@valin4tor valin4tor changed the title Using rewrites in next.config.js causes every page to render twice Using rewrites in next.config.js causes double renders Jan 5, 2022
@balazsorban44
Copy link
Member

Thank you for raising this issue. This is actually expected when you have a rewrites in your config and is vaguely hinted at in the docs here: https://nextjs.org/docs/advanced-features/automatic-static-optimization

During prerendering, the router's query object will be empty since we do not have query information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.

So what is happening in your code is that the page gets statically optimized at build time without query params.

Rewrites can have params that we need to provide in the query e.g. /some/:path so if any rewrites are present, we trigger a re-render iterating over the rewrites to provide the parsed params.

We will try to make this more clear in the docs.

@balazsorban44 balazsorban44 added area: documentation and removed bug Issue was opened via the bug report template. labels Jan 6, 2022
@PsyGik
Copy link

PsyGik commented Jan 10, 2022

Rewrites can have params that we need to provide in the query e.g. /some/:path so if any rewrites are present, we trigger a re-render iterating over the rewrites to provide the parsed params.

Mind providing more implementation details around this please? Specifically, I'm confused by query params that are static and not dynamic.

{
    source: 'home/:path/',
    destination: 'home?dynamic-query=:path',
},
{
    source: 'home/:path/',
    destination: 'home/:path/?static-query=static',
},

Since static-query=static is already present in the destination, can't that be passed as part of the router query?

So what is happening in your code is that the page gets statically optimized at build time without query params.

What about pages that are ISR? Since it's runtime, what are the limitations to access the query params for ISR pages?

@bennettdams
Copy link
Contributor

bennettdams commented Jan 13, 2022

I understand the reason for this to happen, but is there a userland solution?
Especially when using Dates via hydration, this kills referential equality and causes problems all over the place.
If I never use query params, is there a way to opt-out of this behavior?

@ijjk said that isReady could be used:

As mentioned in the issue isReady can be used to defer logic that relies on the query information.

..but in my case, the double render problems have nothing to do with route-specific code or a global store/entrypoint. It's just that a lot of my code relies on hydrated data, so in reality I shouldn't need to check for isReady in every single useEffect where hydrated data is used.


This is a reocurring issue and I would highly appreciate if this is explained in the docs in more detail:

The explanation right now does not make it clear that a double render will be the result of using rewrites.


Context:

In my case, I render huge charts, so a double initial render is causing massive problems. Specifically, I use hydration with React Query and Date objects will therefore trigger useEffects all over the place, because of missing referential equality in the dependency arrays. I could e.g. use ISO strings and work around this problem, but in my opinion, this should not be a problem in the first place.

@valin4tor
Copy link
Author

@bennettdams not directly related to this issue, but have you considered memoizing your components?

@bennettdams
Copy link
Contributor

bennettdams commented Jan 14, 2022

@bennettdams not directly related to this issue, but have you considered memoizing your components?

@valerie-makes My answer is also off-topic now: Sure, I already do. I have a lot of performance optimizations in place, e.g. also useMemo where needed, but no memoization (even React.memo) will help you when the referential equality of variables is not given, as it is the case here with the double hydration.

That's what I meant with...

I could e.g. use ISO strings and work around this problem, but in my opinion, this should not be a problem in the first place.

@bennettdams
Copy link
Contributor

bennettdams commented Jan 31, 2022

Update: Not sure why this not occured to me, but besides the proposed solution of using the router's isReady, you can use memoization to tackle this problem - you just have to do it at the "entrypoint", which is the first render function outside getStaticProps for your route (thanks, @Ephem):

export const getStaticProps = async () => {
  return {
    props: {
      date: JSON.parse(JSON.stringify(new Date())),
    },
    revalidate: 10,
  };
};

// Option 1: Route with date as props
// `date` will change two times
export default function RouteWithProps(props) {
  return (
      <ShowDate date={new Date(props.date)} />
  );
}

// Option 2: Route with date as props, but memoized
// `date` will change one time
export default function RouteWithPropsMemoized(props) {
  // this memoization is the important part
  const date = useMemo(() => new Date(props.date), [props.date]);

  return (
      <ShowDate date={date} />
  );
}

Here's a reproduction which has two roues, one with and one without memoizing at the root render function:

https://stackblitz.com/edit/nextjs-ubqwdg?file=pages%2Findex.js

You have to go to a route directly and not programmatically, otherwise the double render won't trigger this issue.

If you go to /route-with-props and look at the console of the devtools, you can see that date has changed twice intead of once:

image

At the other route (/route-with-props-memoized), this does not happen and date will only change once, as intended.


Same goes for hydration e.g. with React Query - you just have to memoize the dehydrated state in your route's root render function.


I understand the reason for this to happen, but is there a userland solution?

This is no solution to the double-render problem, but a good way to prevent the referential equality problems that come with it - you just have to be aware of it.

@MaedahBatool
Copy link
Contributor

Submitted PR to update these details. :)

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2022

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 9, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants