We went through steps to enable code splitting in Part 1. As mentioned in Part 1, it’s not working right at server-side rendering yet. What exactly the problem is?
Exercise: see the problem with actual code
You can skip this section if you already know the problem or don’t bother to run/test with actual code.
You can see the problem by downloading the Example code in Part 1, Remove the following line at Views > Home > Index.cshtml.
1 |
<script src="/dist/bundle.js"></script> |
Without the above line, the client side rendering is disabled and the page is only rendered at server side so you can see what’s returned from the server. Build and run. Then click on About or Contact.
That’s right, the About or Contact page returns nothing.
Server-side rendering problem with code splitting
Dynamic import of the page module is done asynchronously. That means when the server is rendering the page, the required module is not loaded yet and as a result, a blank page would be returned.
What we need: when loading the initial page with server-side rendering, all modules must be loaded synchronously before rendering.
Goal: To enable code splitting with server-side rendering (with redux-first-router)
Base project code: Continue from Part 1 project code.
Loading code splitting module synchronously
It actually isn’t very difficult to accomplish. With the use of redux-first-router and redux , when the redux store is created by calling createStore() method, the store would be hydrated with route information. We can easily find out the page name to be rendered by the following line:
1 |
var page = Store.getState().location.type; |
With this info handy, we can then synchronously load the corresponding dynamic module (if the rendering page is code splitted) before rendering.
Let’s first add a helper function to load the module syncrhonously to routes.tsx
1 2 3 4 5 6 7 8 9 |
export const syncLoadPage = (page: string) => { var loader = loaders[page]; if (!loader) return Promise.resolve(); return loader() .then(module => { var Component = module.default; Components[page] = <Component />; }); }; |
syncLoadPage() will get the module loader based on page name and return the module as a react component once it’s loaded.
Load module synchronously at server side
Next we make use of this helper function to load module synchronously before rendering at server side. At top of entry-server.tsx, import syncLoadPage() with following line:
1 |
import { syncLoadPage } from './routes'; |
Then place following codes after calling createStore() at entry-server.tsx to load module synchronously:
1 2 |
var page = Store.getState().location.type; addTask(syncLoadPage(page)); |
This takes care of server side. We would need to do the same thing at client side to make sure rendering at both sides are in sync. At top of entry.tsx, import syncLoadPage() same as we did at entry-server.tsx.
Then we replace the following line at entry.tsx
1 |
hydrate(<Provider store={Store}><App /></Provider>, document.querySelector('#content')); |
with
1 2 3 4 5 |
var page = Store.getState().location.type; syncLoadPage(page) .then(() => { hydrate(<Provider store={Store}><App /></Provider>, document.querySelector('#content')); }); |
Similarly, for initial page load, client side will synchronously load the dynamic module before rendering.
And that’s it. Build and run and code splitting should work with server-side rendering.
IMPORTANT: This solution works great when you use simple architecture that does not have nested dynamic imports to render the page. I like to keep things simple and not use/make stuffs that are overly complicated. Note that user triggered dynamic loading (other than route page) does not count as nested dynamic imports because they don’t affect initial page load. If you do need nested dynamic imports, you may want to check out other existing packages that offer more sophisticated asynchronous loading including react-loadable, react-async-component, react-universal-component and etc. A good comparison of these loaders can be found at Anton Korzunov’s post React and Code Splitting made easy.
Example code is available at Github.