First of all let's talk about Next.js.
As doc says:
Next.js a minimalistic framework for server-rendered React applications.
It comes with some handy features. Simplest application setup I have ever seen:
- Possibility to customize webpack configuration;
- HMR for development workflow enabled by default;
- Automatic code splitting;
- Lazy loading;
- Route navigation blocked until its initial props are resolved;
- Simple client-side routing;
So it is simply amazing, it removes much of effort in configuring webpack, babel and other tools and speed up your development workflow (trust me, seeing an edit on a page it's instantaneous!).
Which are project features?
- Server side rendering with Express + react;
- Routing: Express + react-router;
- Bundling: Webpack with Babel and some other custom plugins;
- Lazy loaded modules;
- Route based chunks (this with lazy load was tricky to configure);
- Styling: styled-components + scss;
- State management: Redux + Immutable;
How could we substitute them? Using a framework or a set of tools which handle project's features.
But our needs at brumbrum.it were other than have an all-in-one bundling solution.
After some analysis we defined our main needs:
- Clearer implementation of routes;
- Lazy loading modules with possibility to disable it for SSR;
- Faster development workflow;
- Removing custom webpack plugins;
One day I read about Next.js, features were interesting, and it could satisfy our needs, so we decided to analyze it:
- Routes must be in pages folder and each file under this folder is a route. Each route has a getInitialProps method which until it's not resolved, render is not invoked. This allows to declare route dependencies in one point;
- Next.js support dynamic import by default, and adds some capabilities, such as loader and possibility to import module statically for SSR; Development build is lazy, it means pages are built on demand. Furthermore Next.js holds built pages in memory, so requesting the same page second time is faster;
- Next.js comes with its own webpack configuration which has all you need for a React application, farther, the configuration is extensible;
Resuming, we could have migrated to Next.js. Effort to migrate to Next.js is less than refactoring webpack plugins every time a new Webpack version is released, and integrating configurable lazy lazy loading components.
Starting from the router. Every time we had to add a route we had to change a lot of files. The flow was clear, but it was too brittle. Furthermore we had actions' dispatches in express routes handlers and route components. Two points in which we declared dependencies, not too bad, but as project has began growing we felt the need to unify these declarations.
Next.js comes with file system routing enabled by default, but it didn't satisfy our need, so we have disabled it.We could have use Express as server router, but implementing router with Node.js http module is not too tricky.
What we needed is a URI pattern match and nothing else. So we have created an hybrid router for both server and client which resolves the URI to the route component path.
Now routes configuration looks like this For client-side Next.js provide a Link component. What we needed to do is to pass component path and URI seen in browser to the component.
Next step was moving routes to Next.js pages, so we had to move route dependencies to
getInitialProps method. This has allowed us to divide the behavior of the routes more clearly, removing all the visual behavior in another folder.
After routes refactor, the dirty work began.
First we had to adapt props passed to components, for example Next.js uses 'asPath' as location, while react-router returns an URL object.
Done this, was the moment for Redux! Yeah! I was expecting nothing else. I have to admit it, firstly the solution was not so clear, but Next.js examples had helped us. We have Immutable, so we have to "transpile" state because state returned by transition from server to client is a plain object and we need an Immutable state (List, Set, Map, etc…). I will not explain it, we have used the same implementation as example.
Done props refactoring and Redux integration, we implemented the layout, so we had to integrate styled-components, and it was easy, Next.js allows to edit document's markup, such as head or body, so we needed to put here all old styles and script. Furthermore, we used to edit head tags in "sub-components", before we used react-helmet, now we simple use head module of Next.js.
Most of work was done, good result, development workflow speed up, file architecture clearer, but some strange things we had to patch. To make work jest we had to install a bridge version of babel-core.
Finally, the last step: production build configuration.
All webpack plugins and configuration was deleted, yes, nothing has been hold. Next.js has replaced all of this configuration and with some tuning it handles our codebase.
Now, let's close this amazing trip with some results.
The build time has turned down from 57s to 30s, server response time has decreased of about 900ms (from >1s, so 90% faster), CPU and memory usage are decreased, only negative result is page load time, which increased of about 2 seconds, but we are working on this, it could be anything.