DEV Community

Discussion on: Mobx Server Side Rendering with Next.js

Collapse
 
greggcbs profile image
GreggHume

Thanks, but theres some issues i have noticed with this approach for me.

1 . App bundle size and speed
With mobx it is not necessary to wrap an app with the storeprovider. I have noticed that if your stores get big this bloats the main app bundle size and penalizes on SEO performance. I shaved 40% of my page sizes by removing this.

  return (
    <RootStoreProvider hydrationData={pageProps.hydrationData}>
      <Component {...pageProps} />;
    </RootStoreProvider>
  );
Enter fullscreen mode Exit fullscreen mode

2 . I have noticed that using useRootStore() causes uneeded re-renders
Doing this will cause the component to rerun useRootStore every time an update happens. This is uneeded calling of useRootStore and in big apps will cause performance issues.

import {useRootStore} from "@/core/store/root.store";
function Component(){
    // component will rerun useRootStore everytime there is a change in data
    const {companyStore, listingStore} = useRootStore();

    return (
        <div>
            {companyStore.company.companyID}
        </div>
    )
}
export default observer(Component);
Enter fullscreen mode Exit fullscreen mode

VS

importing the store outside of the component which is way more performant

import {companyStore, listingStore} from "@/core/store/root.store";

function Component(){
   // component will only rerender parts of the dom thats data have changed
   // store will only be imported once because it is imported outside of the component
    return (
        <div>
            {companyStore.company.companyID}
        </div>
    )
}
export default observer(Component);
Enter fullscreen mode Exit fullscreen mode

3 . Hydration on the server?
Now im not sure how to hydrate on serverside using my above approaches but I would rather hydrate in the browser by passing in the data from serverside through props to the store than bloat my app by wrapping it with a provider and slowing it down using the hook. It is any case probable that the store has to run in the browser to initialise itself, and that will probably trigger a dom rerender - im not sure? So that computation is happening anyways which means the app is building twice - on serverside and in the client. Possibly, im not sure.

final thoughts
As I am on a drive for performance and battery life on devices - some of the points above have come to my attention. The above comments are not to degrade your solution but bring forward what I have discovered.

Thread Thread
 
ivandotv profile image
Ivan V. • Edited
  1. You can use mobx without providers or hooks, just import the store, and reference it directly in the component.
    The reason I'm using the provider component and useRootStore hook is because of the testability of the components. Without them, how are you going to substitute (mock) the store when testing the components with something like react-testing-library?
    Root store is used in _app component, everything that is included in the _app component must be bundled for "first page load". If you don't need your store present through the whole app, don't include it in the root store, and load it only on the pages where it is needed, that will decrease the size.
    Question: suggested best practice for holding onto a store constructed with props #74

  2. I believe that you are wrong here because useRootStore can never trigger a render, all it does is return a stable reference to the mobx store (always the same object). The same applies for the root provider component, it will never trigger a render, the only purpose of the provider component and the hook is to enable easier testing (maybe I should have mentioned that in the article). In your example, how are you going to test your component in isolation?

  3. In nextjs, the application always renders twice once on the server, and then on the client, it has nothing to do with Mobx. getServerSideProps and getStaticProps are essentially hydration for react components and the principle is the same. You need to decide if you want to render an "empty" store in the browser when the page loads.
    If care about SEO, then you will render on the server, so the crawler has something to index.

All good questions, keep'em coming :)

Thread Thread
 
greggcbs profile image
GreggHume

1 . That is a good idea but then at some point its not clear what is available in the root context and what is not. But thats development.

2 . I am not doing testing so i didnt think of that, good point. To clarify this unnecesarry rerender discussion, I see you have done storeContext different to another example I was looking at and I got the two mixed up. To clarify your code anyways, does useContext know that you are passing an existing context in?

How much execution happens when this is run:

export function useRootStore() {
  const context = useContext(StoreContext);
  if (context === undefined) {
    throw new Error("useRootStore must be used within RootStoreProvider");
  }

  return context;
}
Enter fullscreen mode Exit fullscreen mode

^ because from what I have seen, this function will run everytime a change happens in a component, if the component is wrapped with mobx observable. And if useContext does some processing when it is invoked then i am worried about this extra processing.

3 . I was not saying mobx causes both renders, i was saying that they happen anyways. Hydration happens anyways.

So if I do this, it has the same effect as what you were doing:

export async function getStaticProps(res) {
    return {
        props: {
            company: data[0].data,
            listingsByCategory: data[1].data.categories
        },
        revalidate: 300,
    }
}

export default function Company({company, listingsByCategory}) {
    companyStore.hydrate({company});
    listingStore.hydrate({listingsByCategory});

    return (
        <>
            <SomeComponentsThatUseTheStoreInternally />
        </>
    );
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
ivandotv profile image
Ivan V. • Edited
  1. useRootStore hook is just a way to get to the root store without explicitly declaring the store inside the component, it's just getting back the value that is stored in context, and if there is no context above, it throws.

3.Yes exactly :) You just need to guard against calling the hydration more than once, currently if will hydrate every time the Company component is rendered.