next-redux-wrapper: next-redux-wrapper usage slows down route change

Describe the bug

Whenever I use next-redux-wrapper, changing routes starts to get really slow.

export const getServerSideProps: GetServerSideProps<I18nProps<MyLocale>> = getStore((store) => async (ctx) => {
// ...
  store.dispatch(setFeatureFlags(flags));
// ...
  store.dispatch(updateDevice(getSelectorsByUserAgent(ctx.req.headers["user-agent"])));
  return ...;
});

With the code above, whenever I change routes, it takes around 10s to change on dev and 5s on prod. If I remove it, it becomes instant, like milliseconds.

To Reproduce

The code is proprietary, I can’t fork or clone it.

Steps to reproduce the behavior:

Just use this library with some dependencies like usedapp, or theming, or any UI lib. Check how long it takes to change routes. Remove it, and you’ll see the difference.

Expected behavior

Using redux shouldn’t impact the app routing load time.

Screenshots

If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 6]

Additional context

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 17 (3 by maintainers)

Most upvoted comments

hi , i had this problem too, you can use 8.0.0-rc.1 instead of 7.0.5 there is some changes

for create store you must use configureStore

const makeStore = () =>
    configureStore({
        reducer: appReducer,
        devTools: true,
    });

export const wrapper = createWrapper(makeStore, {debug: false});

and in _app.js:

const App = ({Component, ...rest}) => {
    const {store, props} = wrapper.useWrappedStore(rest);
    return (
        <Provider store={store}>
                <Component {...props.pageProps} />
        </Provider>
    )
}

I can release it, it’s been in beta for quite some time.

I’ve encountered this issue as well, and in my case it’s because, on client-side navigation with next/link and next/router, identical HYDRATE actions are repeatedly dispatched - 700ish times per navigation for me in dev mode. I think it puts the app into a spinlock until some async task finishes - something, somewhere (possibly not in next-redux-wrapper) is not waiting when it should. I have not found the root cause.

Using React 18.2, Next 12.1.6, next-redux-wrapper 7.0.5. Other possibly relevant libraries are RTK Query with @reduxjs/toolkit 1.8.2 and connected-next-router 4.1.1 - calling them out because they both do interesting things on route change.

Here’s what I found out and a workaround that seems to work for my case, in case they’re useful to you @kirill-konshin or anyone else having this problem. I am using App-level getInitialProps only.

It’s coming from withRedux.shouldComponentUpdate: nextProps.initialState and this.props.initialState are different repeatedly, so this.hydrate is called repeatedly.

I think what’s supposed to happen is: HYDRATE is dispatched, the store is updated, this.props has the updated store, that makes this.props === nextProps, and we move on. But something happens in that process and React tries to re-render before the store is updated, so WrappedApp’s props aren’t updated yet, so it dispatches HYDRATE again. Repeat until… this is where I got stuck.

I overrode WrappedApp.prototype.shouldComponentUpdate with an identical body, with the this.hydrate call in a timeout. 0 timeout was the same, but waiting 10-50ms reduced the number of dispatches and waiting 100-150ms eliminated the duplicate dispatches. (This is why I wonder if RTK Query is involved - 50-100ms is on the network request timeframe.)

In my use case (but not necessarily others!), changing route always changes the Next page. So if we’ve already dispatched a HYDRATE for this.props.Component, we know the store is updated. So my workaround is to remember the last page component we hydrated for, and only hydrate if it’s different from this page component:

const WrappedApp = wrapper.withRedux(App);

let lastComponent;
WrappedApp.prototype.shouldComponentUpdate = function(nextProps, nextState, nextContext) {
  if (nextProps?.pageProps?.initialState !== this.props?.pageProps?.initialState
    || nextProps?.initialState !== this.props?.initialState) {
      if (lastComponent !== this.props.Component)  {
        this.hydrate(nextProps, nextContext);
        lastComponent = this.props.Component;
      }
  }
  return true;
};

My app doesn’t use page-level getInitialProps or getServerSideProps, it doesn’t look like this would break those but I haven’t tested that case. It might break if you render the same page on different routes though!

That RC is pretty good. Thank you @roshaninet