1

This is a topic that's been discussed a lot through github issues and by now I've noticed two main opinions: It's not possible or it should not be done at all. The argument for both sides is that redux is not meant for it, that the .replaceReducer function is only meant for the purposes of hot-reloading (even though redux itself mentions it as a possibility for code-splitting).

The goal

Anyway, what I would like to achieve (ideally) is a system that only sends the relevant slices and relevant redux code for a specific route in NextJs. And (even more ideally) when navigating between pages the store should just get extended and not re-created.

My initial approach

My first idea was to implement a recipe from the link above, attaching and exposing the injectReducer function onto my store during the store setup:

const store = configureStore({
  reducer: {
    globals,
    [rtkqApi.reducerPath]: rtkqApi.reducer
  },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(rtkqApi.middleware)
});

store.dynamicReducers = {};
store.injectDynamicReducer = (name, reducer) => {
  if (Object.keys(store.dynamicReducers).includes(name)) {
    return;
  }
  store.dynamicReducers[name] = reducer;
  store.replaceReducer(
    combineReducers({
      globals,
      [rtkqApi.reducerPath]: rtkqApi.reducer,
      ...store.dynamicReducers
    })
  );
};

const makeStore = () => store;

export const wrapper = createWrapper(makeStore);

export const injectReducer = (sliceName, reducer) => store.injectDynamicReducer(sliceName, reducer);

So basically every page would have a globalsSlice, containing the user info and some other global data, and Redux Toolkit Query API slice (which would then be code-split using RTKQ injectEndpoints functionality).

With this setup, each page that wants to inject its own custom slice (reducer) would do something like this:

const SomePage = () => {
  const someData = useSelector(somePageSliceSelectors.selectSomeData);
  return (
    <Fragment>
      <Head>
        <title>Some Page</title>
      </Head>
    </Fragment>
  )
};

export default SomeRoute;

injectReducer('somePageSlice', somePageReducer);

export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
  // Whatever necessary logic we need
});

Initially this seemed to have worked fine, but then when I realized that next-redux-wrapper works by calling the makeStore factory on every request, and I'm manipulating and mutating a global store object, there has to be something wrong with this, ie a race condition that I haven't been able to cause by testing. Also another problem occurres when using Redux Toolkit Query. For example, if I need to get a cookie from the original request (the one that nextjs receives) and then re-send it to another API endpoint that is handled by redux toolkit query, I would need to extract the cookie from the request context, to which I don't have access unless I do something like this:

export const makeStore = (ctx) => {
  return configureStore({
    reducer: ...,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        thunk: {
          extraArgument: ctx,
        },
      }).concat(...),
  });
};

which further implies that I should definitely not be mutating the global store object.

So then I thought alright, instead of manipulating the global store I could try doing it in GSSP:

export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
         store.injectDynamicReducer('somePageSlice', somePageReducer);
    });

But no luck here, the slice does not get loaded and the state does not get constructed. It is my guess that the Provider in the _app gets rendered before this, but I'm not sure.

In conclusion, I'd like to know whether anyone has tried and succeeded in implementing redux code splitting using RTK, RTKQ and NextJs. Also, I would like to ask an addition question: Is it necessary? What I mean by this is, if I were to not code-split at all, and send all slices on every request, how performance impactful would this be? Also, since I'm not sure exactly how the NextJs bundler works and how code chunking is done: If a certain page receives a slice it doesn't use at all, will it only receive its initial state or all of its logic (all the selectors, reducers and actions)? If not then maybe this isn't so bad, since initial states are just empty objects.

I hope I've presented the problem clearly enough, as it is a very complex problem, but feel free to ask follow up questions if something doesn't make sense.

Thanks in advance.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.