Frontend Architectures | Divide & Conquer
Published: 2023-05-05 11:17:28

## Motivation

In this technical article, I will be sharing our experience of starting a new frontend project and the challenges we faced in determining the best approach to take. Our team was faced with the task of building a new frontend application, but there were doubts about which approach to follow. In order to make an informed decision, I have decided to explore multiple frontend architectures and tools. The goal of this article is to provide an in-depth understanding of the different approaches available, and how we arrived at the best approach for our specific use case. Through this article, we aim to provide valuable insights for other teams who may be facing similar challenges.

## Tools and concepts

### Monorepos

In recent years, monorepos, or monolithic repositories, have become a popular pattern for organizing and managing large codebases. A monorepo is a single code repository that contains all of the code for a project, including multiple different packages or modules. This approach has several benefits, such as making it easier to share code between different parts of a project and simplifying the process of building and deploying code.

One popular tool for managing monorepos is [pnpm workspaces](https://pnpm.io/workspaces). Pnpm Workspaces is a plugin for the Pnpm package manager that enables teams to use Pnpm to manage the dependencies of multiple packages within a monorepo. In this article, we will explore how to set up and use Pnpm Workspaces in a monorepo project.

### SPAs vs MPAs

There are two main approaches to frontend architecture: single-page applications (SPAs) and multi-page applications (MPAs).

A single-page application, as the name implies, is a web application that loads a single HTML page and dynamically updates the content as the user interacts with the application. SPAs use JavaScript to dynamically update the content of the page, rather than loading a new page from the server. This means that the application only needs to load once, and subsequent updates are done via JavaScript and APIs. This approach can result in a more seamless user experience, as the application can update quickly and smoothly without requiring the user to wait for new pages to load.

On the other hand, a multiple-page application (MPA) loads a new HTML page from the server for each new view or route. In an MPA, when the user clicks on a link or submit a form, a request is sent to the server, which generates and sends a new HTML page to the client. The user then needs to wait for the new page to load and render, which can result in a slower experience.

In the past years, SPAs are becoming increasingly popular among web developers due to their ability to provide a fast and seamless user experience.

> It's worth noting that both approaches have their own pros and cons, and the choice of which one to use depends on the specific requirements and constraints of the project.

### React

One of the most popular libraries for building SPAs is [React](https://reactjs.org/), a JavaScript library created by Facebook. React allows developers to create reusable, modular components that can be easily composed to build complex user interfaces.

One of the key features of React is its ability to efficiently update the user interface in response to changes in the data. This is achieved through the use of a virtual DOM, which optimizes the process of updating the view by minimizing the number of changes that need to be made to the actual DOM. React uses a virtual representation of the actual Document Object Model(DOM) to understand which parts of the view needs to be updated, when data changes occur.

React is widely adopted across different fields, from small personal projects to large-scale enterprise applications, and is considered as one of the most widely adopted libraries to build web applications and maintainable codebase.

### React Native & Expo

In addition to web development, React can also be used to build native mobile applications through the use of [React Native](https://reactnative.dev/). 

React Native allows developers to use the same codebase for building apps for both iOS and Android, which can save a lot of time and effort compared to developing separate apps for each platform. React Native uses native components, which are the building blocks of mobile apps, and allows developers to write their own components using JavaScript and React.

While React Native is mainly used for building mobile apps, it's also possible to use it for building web apps. The process of building web apps with React Native is called "React Native Web" and allows developers to use the same codebase for building web apps and mobile apps. This approach allows developers to build web apps that have a similar look and feel as mobile apps, and share the same codebase.

However, React native for web is not a direct port of the React native, but a compatibility layer that allows to use some of the React native features and codebase in web environment. It's not as mature as React and may have some limitations, but it's still a great way of sharing the logic and components between web and mobile apps.

React Native for web can be a powerful tool to build cross-platform applications, and it's definitely worth considering for organizations that want to create web and mobile apps with similar features and a shared codebase, especially for scenarios where some of the native features are not essential.

> Keep in mind that not all React Native components are supported on the web, so you may need to use alternative components or write custom implementations for some features.

[Expo](https://expo.dev/) is an open-source toolchain and platform for building mobile applications using React Native. It provides a set of tools and services that make it easier to build, test, and deploy mobile apps. Expo provides a set of pre-built libraries and components, which can be used to add functionality to your app, such as push notifications, camera access, and more.

### Vite

[Vite](https://vitejs.dev/) is a lightweight development server and build tool that is designed for building modern web applications. It is built on top of native ES modules and the V8 engine, which makes it fast and efficient. Vite is simple to use and requires no configuration, making it a great choice for developers who want to quickly set up a development environment for their project.

Vite also has built-in support for React, meaning that it can be used to quickly set up a development environment for React projects. Vite's development server is able to hot-reload React components, which means that changes in your code will be immediately reflected in the browser without the need to manually refresh the page.

In this article, we'll explore how to use Vite for building React applications. We will start by installing and configuring Vite, and then dive into using it to build a React application from scratch. We will also discuss some of the advanced features of Vite, such as how to use plugins and microfrontends.

> Vite uses the V8 engine's Just-In-Time (JIT) compilation to quickly compile and execute JavaScript code. The JIT compiler in V8 is able to optimize JavaScript code at runtime, which improves the performance of the code. This makes the development experience faster and more responsive.
> In addition, Vite uses V8's snapshot feature to create a pre-compiled version of the JavaScript code, which is then loaded into memory for faster startup times.

### Microfrontends

Another trend in modern frontend development is the use of microfrontends, which are small, independent services that can be composed together to build complex applications. 

Microfrontends is an architectural pattern that aims to decompose a monolithic frontend application into smaller, more manageable parts. It breaks down the monolithic frontend into a set of smaller, independent applications, called microfrontends, that can be developed, tested, and deployed independently. Each microfrontend is responsible for a specific feature or part of the user interface and communicates with the other microfrontends through a well-defined API.

The concept of microfrontends has its roots in microservices, an architectural pattern for building backend applications. Similarly, microfrontends allow for a more modular, scalable, and maintainable frontend architecture, as well as faster development, deployment, and release cycles.

Microfrontends can be implemented in different ways, such as using iframes, web components, or container applications. The main idea is to separate the different parts of the application into independent, reusable components that can be developed and deployed independently.

> Note that building a React application with microfrontends also comes with its own set of challenges. One of the main challenges is managing the communication and coordination between the different services, which can become complex as the number of services increases. Additionally, monitoring and testing a microfrontends-based application can also be more difficult, as there are more moving parts to keep track of.

In this article, we will explore the concept of microfrontends using Vite and React.

### So now, let’s put it all together

I have created a [repository](https://github.com/hugo8barbosa/react-vite-monorepo), using a monorepo with pnpm workspaces, containing examples of the various frontend architectures, technologies, and concepts discussed above, including SPAs, MPAs, microfrontends built with React, and integration of React Native for Web using Vite in the building process.

The repository also includes an example of a React Native app built with Expo, using components from an internal shared library.

---

## The project

This [repository](https://github.com/hugo8barbosa/react-vite-monorepo) is structured this way:

```
react-vite-monorepo
├─ apps
│  ├─ react-multi-page-app // Vite React App (MPA)
│  │  ├─ apps
│  │  │  ├─ react-multi-page-app-1 // Vite React App (SPA)
│  │  │  ├─ react-multi-page-app-2 // Vite React App (SPA)
│  ├─ react-native-expo-app // Expo app
│  ├─ react-vite-app // React and Vite App (SPA)
│  ├─ react-vite-microfrontends-app // React and Vite App with Microfrontends
│  │  ├─ apps
│  │  │  ├─ react-vite-microfrontends-app-1 // Vite React App (SPA)
│  │  │  ├─ react-vite-microfrontends-app-2 // Vite React App (SPA)
│  │  │  ├─ react-vite-microfrontends-app-3 // Webpack React App (SPA)
├─ packages
│  ├─ shared // Internal shared library
│  │  ├─ src
│  │  │  ├─ components
│  │  │  │  ├─ Card // React Native component used in all the apps
│  │  │  ├─ hooks
│  │  │  ├─ utils
package.json
pnpm-workspace.yaml
...
```
Let’s take a look step by step to see the purpose of each app and its configuration.

📁 **[root](https://github.com/hugo8barbosa/react-vite-monorepo)** 

The root directory contains all the files responsible for the global configuration. I'd like to highlight two of them:

`package.json` 
It contains all the package's metadata, including global dependencies and scripts.

`pnpm-workspace.yaml` 
Defines the root of the workspace and enables you to include/exclude directories from the workspace. By default, all packages of all subdirectories are included.

📁 **[packages/shared](https://github.com/hugo8barbosa/react-vite-monorepo/tree/main/packages/shared)**

The `packages` folder contains logic and components that are shared and used by multiple other packages within the repository. With this is possible to share code and dependencies between packages, reducing duplication of code and making it easier to maintain and update the shared code across the entire codebase. 

This package exports a React Native component, a custom React hook, and a simple function that picks a random city from a static array.

📁 **[react-vite-app](https://github.com/hugo8barbosa/react-vite-monorepo/tree/main/apps/react-vite-app)**

In this folder, we have a simple example of a React app built with Vite that is using elements provided by the shared package.

This app was generated using a React Vite template through the prompt `pnpm create vite` and the shared package was also installed `pnpm add react-vite-monorepo-shared`. However, Vite requires some extra configurations when using internal dependencies in order to resolve them. Example below:

![[vite.config.ts](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-app/vite.config.ts)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yxrml6l4jimfp1pl7uep.png)

Once the setup is ready we can start building our React app using React Native components. Like this:

![[App.tsx](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-app/src/App.tsx)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/57ob3ol50hmcmqvuxia8.png)

Then with just some styling, we get the final result:

![SPA built with Vite and React using a React Native component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5abynif12zmstiintie.png)

This app, despite being simple, is mainly to show an example of how to install and use an internal package and React Native components in a web application. 

Also, it’s going to be used as a base application in the `react-multi-page-app` and `react-vite-microfrontends-app examples`.

📁 **[react-multi-page-app](https://github.com/hugo8barbosa/react-vite-monorepo/tree/main/apps/react-multi-page-app)**

```
react-vite-monorepo
├─ apps
│  ├─ react-multi-page-app
│  │  ├─ apps
│  │  │  ├─ react-multi-page-app-1
│  │  │  ├─ react-multi-page-app-2
│  │  ├─ src
│  │  │  ├─ components
│  │  │  │  ├─ nav
│  │  │  ├─ main.tsx
│  │  │  ├─ ...
```

This folder contains a multi-page app built with React and Vite that contains three single-page applications (`main, react-multi-page-app-1, and react-multi-page-app-2`) that are loaded depending on the URL.

In this structure, `src/main.tsx` is the entry point for the main app and Vite is responsible for determining which SPA to load based on the URL using the configuration on `vite.config.js`. 

- `http://localhost:4000/` → loads `main app`
- `http://localhost:4000/apps/react-multi-page-app-1/` → loads `react-multi-page-app-1`
- `http://localhost:4000/apps/react-multi-page-app-2/` → loads `react-multi-page-app-2`


![[vite.config.ts](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-multi-page-app/vite.config.ts)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ws5cqqauzu6h1ctf59u5.png)

It does this by using the `react-multi-page-app, apps/react-multi-page-app-1, and apps/react-multi-page-app-2 folder`s, which contain the code for each SPA. 

The `components` folder contains the navigation component used to navigate between apps.


![MPA built with Vite and React](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxdlqimjytinexsopzmb.png)

📁 **[react-native-expo-app](https://github.com/hugo8barbosa/react-vite-monorepo/tree/main/apps/react-native-expo-app)**

This folder contains a react native app built with Expo using the prompt `npx create-expo-app react-native-expo-app`.

This app is also using components from the shared package.

> Keep in mind that Expo project in monorepos require some extra configuration. You can find more information [in the official documentation](https://docs.expo.dev/guides/monorepos/).

![React Native app built with Expo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atatemx5xnprui7k261s.png)


📁 **[react-vite-microfrontends-app](https://github.com/hugo8barbosa/react-vite-monorepo/tree/main/apps/react-vite-microfrontends-app)** 

This folder contains an example of a React app built with Vite hosting three React microfrontends. Two of them are also built with Vite and one with Webpack. 

- `hosting app` → Vite
- `react-vite-microfrontends-app-1` as `app-1` → Vite
- `react-vite-microfrontends-app-2` as `app-2` → Vite
- `react-vite-microfrontends-app-3` as `app-3` → Webpack

In this example, we’ll need to install and configure `@originjs/vite-plugin-federation`. This package was built based on [module federation](https://github.com/module-federation) to handle microfrontends with Vite.

Module federation is a feature of Webpack that allows multiple independently-built JavaScript applications (known as "modules") to work together as a single application.

When building microfrontends, one of the main challenges is how to organize and manage the different pieces of the application. Without module federation, each microfrontend would have to be built and deployed as a separate application, with its own set of dependencies and configuration.

In practice, using module federation typically involves setting up an "entry" or "shell" application that serves as the main entry point for the user. In this example, different apps will be rendered depending on the URL.

> This app was built based on [Module federation](https://github.com/module-federation/module-federation-examples) and [Vite plugin federation](https://github.com/originjs/vite-plugin-federation) examples. You can find other approaches that may be more suitable for your use cases.

Let's take a look at the configuration files to see how this works.

**Hosting/Shell app:**

- `vite.config.ts` → resolve local dependencies and map apps
- `Router.tsx` → provide different components based on the URL
- `App1.tsx` → as we’re dealing with nested routing and each microfrontend has its own router, is necessary a `navigation manager` that handles the communication between the `hosting app` and `app-*`.


![[vite.config.ts of hosting app](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-microfrontends-app/vite.config.ts)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qvkohi1kmgj672ozzft.png)


![[Router.tsx](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-microfrontends-app/src/routing/Router.tsx)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ms2obxuugt501m0rfuf.png)


![[App1.tsx](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-microfrontends-app/src/components/pages/App1.tsx)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7pcc5a8xmpiyiiubvrz.png)

The structure of the microfrontends is basically the same. The main difference is that `App 3`, which uses Webpack for the building process, has a  `webpack.config.js` file instead `vite.config.ts` as the other two that are using Vite.

**Microfrontends**

- All
`App.tsx` → handles the communication with the `hosting app` to render the matching page
- App 1 and App 2
`vite.config.ts` → resolve local dependencies and configure the module
- App 3
`webpack.config.js` → resolve local dependencies and configure the module


![[App.tsx of all the apps](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-microfrontends-app/apps/react-vite-microfrontends-app-1/src/App.tsx)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7uj143vtpckaajbpx1r.png)


![[vite.config.ts of App 1 and App 2](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-microfrontends-app/apps/react-vite-microfrontends-app-1/vite.config.ts)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe1w61dkc0xmj5bp6yt1.png)


![[webpack.config.js of App 3](https://github.com/hugo8barbosa/react-vite-monorepo/blob/main/apps/react-vite-microfrontends-app/apps/react-vite-microfrontends-app-3/webpack.config.js)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q6qoptq0g5dikf7mwvac.png)

---

## Pros & Cons

### Monorepos

**Pros**

- Code reuse
- Simplified dependency management
- Better collaboration
- Simplified build and deployment

**Cons**

- Increased complexity
- Slower performance
- More difficult to scale


### SPAs

**Pros**

- Faster and more responsive user experience
- Improved offline support
- Better for implementing complex logic

**Cons**

- Increased load time
- More complex development process
- Accessibility challenges


### MPAs

**Pros**

- Simplified development process
- Improved SEO
- Better for simple and static applications

**Cons**

- Slower user experience
- No offline support
- Complex state management
- More requests, more data transferred

### React Native

**Pros**

- Cross-platform development
- Native performance
- Large community and open-source ecosystem
- Hot Reloading

**Cons**

- Limited access to some native features
- Some performance issues
- Steep learning curve
- An additional consideration to design and layout


### Expo

**Pros**

- Easy to use
- Access to a variety of pre-built libraries and components
- Hot Reloading
- Built-in over-the-air updates

**Cons**

- Limited access to some native modules
- Increased initial load time
- Limited flexibility
- An additional consideration of app size


### Vite

**Pros**

- Easy to use
- Lightweight and fast
- Hot Reloading
- Flexible

**Cons**

- Limited built-in features
- Limited support for older browsers
- Additional setup and configuration
- Limited to web development


### Microfrontends

**Pros**

- Modular and reusable components
- Faster development, deployment and release cycles
- Improved scalability and maintainability
- More autonomy for teams

**Cons**

- Increased complexity
- Increased testing effort
- Higher development costs

---


## Conclusion

In conclusion, the world of web development is constantly evolving, and new technologies, tools, and patterns are constantly emerging. Each one of these tools has its own unique set of advantages and disadvantages, which make them more or less suitable for different types of projects. This article has explored several of these technologies and patterns, including monorepos, single-page applications, multiple-page applications, React Native, Expo, Microfrontends, and Vite.

As a developer, it's important to be aware of the different technologies and patterns that are available and to understand the advantages and disadvantages of each one. By understanding the trade-offs, you'll be able to make informed decisions about which technologies and patterns to use for specific projects.

As mentioned before, this article is based on an investigation in order to decide which architecture and tools we should use for our use case. We’ve decided to work on a multi-repo using React and Vite as tools. Our project is built with multiple SPAs, having a hosting app with shared components, logic, and a global router that points to a specific app. These nested apps have their own internal router, components, and logic.
The React Native experiment was mainly to test the integration due to the possibility of having a mobile version of our platform in the future.

Thank you for reading this article, I hope that it has provided you with valuable insights and information about the different technologies and patterns discussed.