React Application Architecture: Code splitting [Part 2]

Written by iliakniazev | Published 2021/06/08
Tech Story Tags: react-app-architecture | architecture-of-react | reactjs | react-code-splitting | microservices | microfrontend | monoliths-to-microservices | react-tutorial

TLDR Part 2: Code-splitting: architectural approaches, microservices, and microfront-ends. Microfront-end is an architectural approach during which independent applications are collected into one large application. It makes it possible to mix different widgets or pages written by different teams using different frameworks in one application (see the figure below) Microservices can reduce testing and deployment time, app complexity, use modular approach and do different tasks in parallel. With micro-services you can reduce test time, deployment time and use a modular approach. We can setup one mega-website and call it a Single App, in which we can create more mini-applications.via the TL;DR App

In the previous part, we I've shared my knowledge and experience about the components and how it affects our React app architecture.
Today we will dive deep into code-splitting: architectural approaches, microservices, and microfront-ends.

Architectural approaches

Every modern web-app uses JavaScript due to its open-source community, standards, applicability, tools and many other things which make JavaScript stand out from the crowd. In bunch of projects it leads to an increase in complexity, development time, testing time, release cycle and entry level for developers. Most often monoliths have such problems as they are large apps.
Monolithic architecture is an architectural approach which blends the main app logic and server-related code so that application consists of a one-layer combination of different components.
I have an extensive experience in developing monoliths and I found out several cons of handling them:
  • significant amount of legacy code
  • a lot of logic is mixed through the whole app
  • different components have similar naming
  • easy to brake something down
  • a bunch of duplicated code
Using micro-service architecture approach, instead of monoliths, we divvy up our app into a set of small, reusable modules that interact with each other on their own layers. One of the main advantages of a micro-services is that your apps becomes flexible and you can easily use oppropriate tech stack for every single task.
Moreover, with micro-services you can reduce testing and deployment time, app complexity, use modular approach and do different tasks in parallel. In case of UI development this approach is named as microfront-ends.
Microfront-end is an architectural approach during which independent applications are collected into one large application. It makes it possible to mix different widgets or pages written by different teams using different frameworks in one application (see the figure below).
Relying on my professional experience I can list the main advantages of the microfront-end approach:
  • use pages and widgets as completely independent applications
  • easily test isolated parts of functionality
  • parallel deployments
Speaking about cons, I can mention the following:
  • application complexity increases
  • huge difference in the JS bundle size in comparison with monolithic application
  • issues with caching and versioning (use lerna!)
On the one hand, using this approach in small projects can be not reasonable. On the other hand, large projects with distributed teams could find more profitable building microfront-end apps. This is the reason why today the microfront-end architecture is widely used by many large companies in their web applications.

Code splitting

Multiple Apps
Here we have several independent applications - with our own webpack, some states, routes, and components that we share between them.
For example, single sign-on: you are authorized in one application and remain authorized between all applications of the platform and technically you are on the same website. But you lose the state and cannot share the state in this case, because these are different domains, different websites, but they have common components and they look the same:
Single Apps
We can setup one mega-website and call it a Single App, in which we can create more mini-applications due to chunks, by splitting code into pieces. It all looks like a graph: we have one level (authorization), work with data is another level, analytics, admin panel... they're nested:
And how it works under the hood:
We have a kind of common (shared) application. This app knows how to work with permissions and redirects, handles state, cookies, and auth errors by redirecting you to the auth page. By the way, auth can also be a separate application that lives independently, which has its own modules, state, routes, and its own localization.
Just imagine that the website has 1000+ pages and you have 1000+ localizations and your localization file size more than the entire website size. Fortunately, it is well handled by Webpack and chunks so as you haven't download unnecessary things:
import(/* webpackChunkName: "sign-in" */ './features/SignIn')
import(/* webpackChunkName: "localize" */ './utils/Localize')
...
import(/* webpackChunkName: "chunkName" */ './path')
State managing
Nowadays, there are a bunch of state managers like Redux, MobX, Flux, Rx.js, etc.. The main advantage is that you can use dev tools and watch the state change in a time-lapse:
In the real life, everything can be resolved with the Context or GraphQL or some other library or approach.
The typical Redux data flow looks like this:
Let's say there is some kind of data flow, we need to make a request to the server. We have some kind of choice of data streams (web-rtc protocol, http, sockets), there is a kind of abstract layer above the API that handles api-requests, adapters that map data into a normalized structure. There can be an action, store, reducer, selectors. Then we pass this data in props to the component.
Well, all this can be easily split:
Here we have an app and we can easily share the state so that each module will have its own. As I said before, we should represent it like a graph to avoid building a kind of plain architecture and website.
For example, in Redux we have a provider that shares the state to the whole app:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'

import { App } from './App'
import createStore from './createReduxStore'

const store = createStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Conclusion

Web-development, as well as architectural approaches and tools, is growing every day. Microfront-end architecture, like any other architecture approach, has it's own pros and cons. You should design app architecture according to your team, project and app needs. Anyway, it's better to keep this in mind and be preprapared for any circumstances and projects.
In the next part, we will cover the frontend metrics and develop our own migration plan from the old application. So stay tuned and have a nice hacking!

Written by iliakniazev | Software developer with a deep expertise in web-development. Current company - June Homes (ex. - Tinkoff Bank)
Published by HackerNoon on 2021/06/08