Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Building Micro Frontends with React 18

You're reading from  Building Micro Frontends with React 18

Product type Book
Published in Oct 2023
Publisher Packt
ISBN-13 9781804610961
Pages 218 pages
Edition 1st Edition
Languages
Author (1):
Vinci J Rufus Vinci J Rufus
Profile icon Vinci J Rufus

Table of Contents (19) Chapters

Preface 1. Part 1: Introduction to Microfrontends
2. Chapter 1: Introducing Microfrontends 3. Chapter 2: Key Principles and Components of Microfrontends 4. Chapter 3: Monorepos versus Polyrepos for Microfrontends 5. Part 2: Architecting Microfrontends
6. Chapter 4: Implementing the Multi-SPA Pattern for Microfrontends 7. Chapter 5: Implementing the Micro-Apps Pattern for Microfrontends 8. Chapter 6: Server-Rendered Microfrontends 9. Part 3: Deploying Microfrontends
10. Chapter 7: Deploying Microfrontends to Static Storage 11. Chapter 8: Deploying Microfrontends to Kubernetes 12. Part 4: Managing Microfrontends
13. Chapter 9: Managing Microfrontends in Production 14. Chapter 10: Common Pitfalls to avoid when Building Microfrontends 15. Part 5: Emerging Trends
16. Chapter 11: Latest Trends in Microfrontends 17. Index 18. Other Books You May Enjoy

Introducing Microfrontends

We are coming full circle with microfrontends! During the Web 1.0 era, websites primarily comprised single pages built in ASP, JSP, or PHP, where we could make changes to each individual page and upload it to a server via FTP and it was immediately available to consumers. Then came the Web 2.0 era and the notion of web apps and Single-Page Apps (SPAs), where we compile, transpile, and deploy large monolithic apps. Now, we seem to be going back to working with smaller apps and pages.

The early 2000s brought in the era of Web 2.0 and the notion of web apps. A few years later, JavaScript frameworks allowed you to build SPAs that updated instantly and didn’t reload a new page each time the user clicked on a link or a button. SPAs were indeed fast for small to medium-sized apps, but as teams went full throttle with building large-scale SPAs, and as applications and teams grew, the velocity and speed of development dropped significantly. Teams seemed to be debating about folder structures, state management, and breaking each other’s code, due to centrally managed libraries and so on. These large SPAs also started becoming less performant due to the large bundle sizes of these apps. More importantly, the high execution time required to parse these JavaScript bundles made the apps even more sluggish on low-end devices and mobile phones. That’s when developers and architects started looking for solutions to these problems. Thankfully, they didn’t have to look too far.

You see, the backend teams went through the exact same problems with the large backend monoliths a few decades back and moved toward the microservices architecture pattern in order to solve their performance and scaling challenges. The frontend teams now look to apply the same principles of microservices to their frontend apps, which are being referred to as microfrontends.

The journey for backend teams toward microservices has been a very long one, spanning multiple decades, and many teams still struggle with it. However, thanks to a lot of debates, discussions, thoughts, leadership, and sharing learning from various microservice implementations, there is an overall maturity to and consensus around microservices architecture.

Frontend teams are just waking up to the notion of microfrontends, and there are multiple schools of thought on what defines a microfrontend, including, in fact, whether microfrontends are even a good thing or not. It will take a couple of years, if not a decade, before there is some consensus around microfrontends. The good thing, however, is that we can learn a lot from the journey of microservices, as a lot of principles and architecture patterns of microservices also apply to microfrontends.

In this chapter, we’ll start by understanding the need for microfrontends. We will cover the definition of microfrontends, and then the different patterns of microfrontends. We will also look into the parameters that will help us choose which pattern to go with for designing your apps. Finally, we will create our very first microfrontend.

In this chapter, we will cover the following topics:

  • Defining Microfrontends
  • Understanding Microfrontend patterns
  • Choosing a suitable pattern
  • Hello World with Microfrontends

By the end of this chapter, you will have a better understanding of two of the most common patterns for building microfrontends and a guide to help you decide which one would be most suitable for you.

Toward the end of this chapter, we will build out a simple multi-SPA microfrontend example and get a feel for how we navigate between the the different SPAs.

Technical requirements

As you go through the code examples in this chapter, you will need the following:

  • A PC, Mac, or Linux desktop/laptop with at least 8 GB of RAM (16 GB preferred)
  • An Intel chipset i5+, AMD, or an Apple M1 + chipset
  • At least 256 GB of free hard disk storage

You will also need the following software installed on your computer:

  • Node.js version 16+ (use nvm to manage different versions of Node.js if you have to).
  • Terminal: A modern shell such as zsh, iTerm2 with oh-my-zsh for Mac (you will thank me later), or Hyper for Windows (https://hyper.is/).
  • IDE: We recommend VS Code.
  • npm, yarn, or pnpm. We recommend PNPM because it’s fast and storage efficient.
  • Browser: Chrome/Microsoft Edge, Brave, or Firefox (I use Firefox).

The code files for this chapter can be found here: https://github.com/PacktPublishing/Building-Micro-Frontends-with-React.

Defining Microfrontends

In this section, we will focus on defining what microfrontends and their key benefits are, and also become aware of the initial upfront investments associated with setting up microfrontends.

The currently accepted definition of a microfrontend is as follows.

“Microfrontends are a composition of micro apps that can be independently deployed and are owned by independent teams responsible for delivering business value of a focused area of the overall application”.

The keywords in this definition are independently deployed and independent teams. If at least one of these terms doesn’t apply to you or your team, then you probably don’t need a microfrontend. A regular SPA would work out to be more efficient and productive. As we will see later, microfrontends come with a bit of upfront complexity and may not be worth it unless you have a large application, where sections of the app are managed by individual teams.

We’ve noticed that some teams that are on their journey to implementing microfrontends misinterpret the micro part of microfrontends and believe an application doesn’t follow a microfrontend architecture unless it’s broken down to its smallest level. They break down their apps into really small apps, which adds a lot of unnecessary complexity. In fact, it negates all the benefits that microfrontends are supposed to deliver.

In our opinion, it actually works the other way around. When breaking down an application into micro apps, the teams should ideally look to identify the largest possible micro app or micro apps that a scrum team can independently manage and deploy to production without impacting other micro apps.

The key takeaway from this is not to be swayed by the term “micro” but instead identify the largest possible app that can be independently deployed by a single scrum team.

Before we go deeper into the wonderful world of microfrontends, it is important to remember that every application doesn’t need to be a microfrontend. Let’s learn more about this in the following section.

Understanding the Microfrontend Premium

Martin Fowler talks about the microservice premium. This refers to the fact that microservices come with a bit of overhead and complexity, mainly in terms of the initial setup and the communication channels between the services. Martin goes on to say that the benefits of a microservices architecture only start showing when size and complexity boosters kick in. To understand this, let’s look at the following diagram:

Figure 1.1 – The microservice premium graph (source: https://martinfowler.com/bliki/MicroservicePremium.html)

Figure 1.1 – The microservice premium graph (source: https://martinfowler.com/bliki/MicroservicePremium.html)

The preceding diagram is a graph of the productivity versus the complexity of an application and depicts the drop in productivity for a monolith SPA and microfrontend as complexity grows.

The same holds true for the microfrontend architecture. The whole process of decoupling the various parts of components, routing, and templates and delegating them to different systems can become an unnecessary overhead for small or medium-scale apps.

The benefits of microfrontends kick in only when your project starts reaching the size and complexity thresholds shown in Figure 1.1.

Exploring the benefits of Microfrontends

All the benefits of a microfrontend architecture are linked to size and scale. Having said that, the following benefits of microfrontends hold true only for apps that are built and supported by teams with over 15 people.

In the following sections, we will learn about the benefits that teams can expect when they implement a microfrontend architecture, all of which are directly linked to improved productivity and better developer experience for team members.

Faster development and deployments

One of the main drawbacks of monolithic Single Page Apps is that as the application and team sizes grow, feature development and deployments come to a crawl. We notice the team spending a lot more time where one team is waiting on the other team to finish something before the application can be deployed. With a microfrontend architecture, every scrum team works independently on their micro app, building and releasing features without having to worry a lot about what other teams are doing.

Easier to scale as the application grows

A microfrontend architecture is all about composing smaller micro apps, so as the application grows in size, it’s just a question of adding additional micro apps and having a scrum team own it.

Now, since each team deals with a smaller micro app, their team members need to spend less time understanding the code base and should not get overwhelmed or worried about how their code changes will impact other teams.

Microfrontends allow one to scale up very quickly, with scrum teams working in parallel once the base microfrontend framework is set up.

Improved Developer Experience

With isolated, independent micro apps, the time required for each team to compile, build, and run automated unit tests for their part of the micro apps is greatly reduced. This allows teams to build and deliver features a lot faster.

While teams run isolated unit and automation tests for their micro apps more frequently, we recommend running full regression suites of end-to-end tests on demand or before committing the code to Git.

Progressive upgrades

The frontend ecosystem is the fastest-evolving ecosystem. Every few months, a new framework or library springs up that is better and faster than the previous one. Having said that, there is always an urge to rewrite your existing application using the latest framework.

With large applications, it’s not possible to easily upgrade or introduce a new framework without rewriting the entire application. The cost of rewriting the application and the associated risks of introducing bugs due to the rewrite are far too high. Teams keep deprioritizing the upgrade and within a few years, they find themselves working on an outdated framework.

With microfrontends, it is easier to pick up one small micro app and upgrade it or rewrite it and then gradually roll it out to other micro apps. This also allows teams to experience the benefits of the new change and learn and course-correct as they migrate the new framework to the other micro apps.

As we move on to the next section, let’s quickly recap some of the key points that we’ve learned so far:

  • Microfrontends are suited for building large-scale apps where teams are set up as full-stack teams, where the backend developers, frontend developers, product owners, and so on are within the same scrum team.
  • Microfrontends have numerous benefits, such as team independence, features launched with improved velocity, and better developer experience. However, these benefits will start becoming visible once you have overcome the initial phase of complexity associated with the “microfrontend premium.”

Understanding Microfrontend patterns

When it comes to microfrontends, there are way too many interpretations. These are still early days for microfrontends, and there is no right or wrong way of building them. The answer to any technical/architectural question is “It depends….” In this section, we will focus on two of the most common patterns that teams adopt while building microfrontends. We will see what key factors to consider when deciding which pattern may be right for you. We will end this section by building a really basic microfrontend to get the ball rolling.

At a very high level, there are two primary patterns for microfrontends. Both of these patterns can be applied irrespective of whether you are building a Server-Side-Rendered (SSR) app or a Client-Side-Rendered (CSR) app. To better illustrate these patterns, we will take the use case of an e-commerce application such as Amazon.

In the following subsections, we will look at these two patterns and how they differ from each other.

The Multi-SPA Pattern

The first pattern that we will discuss is the multi-SPA pattern. As the name suggests, the application is built up of multiple SPAs. Here, the app is broken down into 2-3 distinct SPAs and each app is rendered at its own URL. When the user navigates from one SPA to another, they are redirected via a browser reload. In the case of an e-commerce application, we could look at the search, product listing, and product details as one SPA, and the cart and checkout as the other SPA. Similarly, the My Accounts section, which includes the login, registration, and profile information, would form the third SPA.

The following figure shows an illustration of a multi-SPA pattern microfrontend for an e-commerce app:

Figure 1.2 – Multi-SPA pattern microfrontend for an e-commerce app

Figure 1.2 – Multi-SPA pattern microfrontend for an e-commerce app

As you can see from the preceding figure, our e-commerce application consists of three SPAs: the Catalog SPA, the Checkout SPA, and the Accounts SPA.

In the simplest form of this pattern, each app behaves as an independent SPA that sits within its own unique global URL.

Each SPA is deployed at a unique global route. For example, the catalog app would be deployed at a URL such as mysite.com/catalog/* and all subsequent secondary routes within the catalog app will load up as an SPA within the /catalog/* route.

Similarly, the accounts app would live in the global route of mysite.com/accounts/ and the different pages within the account’s app login, signup, and profile would be available at URLs such as mysite.com/accounts/login or mysite.com/accounts/register.

As mentioned earlier, when the user moves from one macro app to another, there will be a reload of the page in the browser. This is because we usually use the HTML href tags to navigate between the apps. This browser refresh is perfectly fine. I’ve seen teams go to great lengths, complicating their architecture, to try to achieve a single-page experience. The truth, however, is that users don’t really care if your app is an SPA or a Multi-Page App (MPA). As long as the experience is fast and non-janky, they are happy.

At times, the browser reload may work in your favor as it will reduce the risks of memory bloat due to either memory leaks or too much data being put into a data store.

However, if you really want to nail that SPA experience, then you can always create a thin app shell that hosts the global routes and data store, such that each app is called within this app shell. We will be going into more detail of this pattern in the upcoming chapters.

In this pattern, the routing is generally split into two parts, the global or primary routes, which reside within the app shell, and the secondary routes, which reside within the respective apps.

The following figure shows an example of a multi-SPA with an app shell:

Figure 1.3 – A multi-SPA pattern with an app shell to give an SPA experience

Figure 1.3 – A multi-SPA pattern with an app shell to give an SPA experience

Here, you will notice that we have introduced the notion of an app shell, which incorporates the header component, and the different SPAs load within the content slot. This pattern gives a true SPA experience as the header component doesn’t refresh when transitioning from one SPA to the other.

The Micro Apps Pattern

The other pattern for building microfrontends is what we call the micro apps pattern. The reason we call it the micro apps pattern is that this is a more granular breakdown of the application.

As you can see in Figure 1.4, the web page is composed of different components where each component is an independent micro app that can exist in isolation and work in tandem with other micro apps as part of the same page.

Figure 1.4 – Micro app architecture with product images and recommended products co-existing as different micro apps

Figure 1.4 – Micro app architecture with product images and recommended products co-existing as different micro apps

You will notice the preceding diagram is a more granular version of Figure 1.3, where we further break down the central content slot into smaller micro apps. Notice how the central content area now consists of two micro apps, namely the product details and recommended products micro apps.

The micro apps pattern is a lot more complex than the multi-SPA pattern and it is recommended mainly for very large web applications, where there are multiple teams that own different elements on a single page.

In Figure 1.4, we would assume that there is a dedicated team that manages the product description component of the page, and another team that manages the product recommendations component on the same page.

We would also assume that the frequencies at which these components get updated with feature enhancements would be different; for example, the recommendations micro app would constantly undergo A/B tests, and hence would need to be deployed more frequently than the product image and description micro app, which may not change as often.

In this pattern, all the routes, both primary and secondary, are managed by the app shell. Here, in addition to managing the routing and global states, the app shell also needs to store/retrieve information about the page layout for each of the routes and the different micro apps that need to be loaded within each of the pages.

In most cases, such large apps usually have a Content Management System (CMS) in place or a templating engine where the layout and the component tree are stored and served to the frontend.

To summarize, as we come to the end of this section, we saw two primary patterns for building microfrontends, the multi-SPA pattern and the micro apps pattern. These patterns primarily differ in the level of granularity at which you break down your application, and how routing is managed within the microfrontend architecture.

In the next section, we will look at the guidelines that will help you choose the right pattern.

Choosing a suitable pattern

Now that we have a broad understanding of the two patterns of microfrontends, let’s spend some time on some of the key considerations that will help you decide which pattern to go with.

While there may be numerous points of view on what is right, how far to think into the future, and how to future-proof your app and architecture, we believe there are two primary factors that will help you decide on which of the two patterns to go with for your microfrontend architecture. Let’s look at them in detail in the following sections.

Team Composition

For teams that build applications on microservices and microfrontends, it is a common practice that they are vertically sliced based on business functionality. In the e-commerce example, we may have a team that focuses on the browsing journey and another team that focuses on the checkout journey. If one scrum team owns the entire browser journey and one scrum team owns the entire checkout journey, then it is recommended that you go for the multi-SPA pattern. However, if you have numerous small teams that own different entities of the business domain, such as, say, search, product recommendations, and promotions, then it would be wise to go for the micro apps pattern. As mentioned earlier, the rule of thumb is for each scrum team to ideally own a single micro app.

Frequency of Deployments

Another factor that would come into play when deciding how to break down your microfrontend would be the frequency of deployments. If there are specific sections of the app that change more than others, then those sections can be separated into its own microfrontend, which can be separately deployed without affecting the other sections of the app. This reduces the amount of testing that needs to be done because now we need to test only the micro app that is being changed and not the entire application.

As we can see, the decision on whether you should go for a multi-SPA pattern or the micro apps pattern boils down to the two key factors of team composition and deployment frequency, and this is directly related to the two keywords from the definition of microfrontend, namely, independent teams and independent deployments.

Hello World with Microfrontends

OK, it’s time to get our hands dirty writing some code. We are going to start simple by building a basic multi-SPA pattern app. In this example, we will use Next.js, which is currently the most popular tool for building performant React applications. Follow these steps:

Note

For the rest of this chapter, we assume you are using pnpm as the package manager. If not, replace pnpm with npm in the respective commands.

  1. Let’s start by creating a root folder for our app. We’ll call it my-store. Run the following command in your terminal:
    mkdir my-store
  2. Now, let’s cd into my-store and create our two Next.js apps, namely, home and catalog, by typing the following commands in our terminal:
    cd my-storepnpm create-next-app@12

    Or, we can type the following:

    cd my-storenpx create-next-app@12
  3. When it prompts you to add a project name, call it home. It will then go through the various steps and complete the installation.

    The interesting thing about create-next-app is even through you define the version as @12, it will nevertheless pull the latest version of Next.js, hence to ensure consistency with the rest of this chapter we will update the version of next in package.json as follows:

     "dependencies": {    "next": "12",
        "react": "18.2.0",
        "react-dom": "18.2.0"
  4. Now delete the node_modules folder and the package lock file and run the pnpm i command

Important note

While you can always use yarn or npx to run the CLI, we recommend using pnpm as it is 2-3 times faster than npm or yarn.

  1. Once it’s done with the setup, go ahead and create another app repeating steps 2-5. Let’s call this project catalog.

    Once complete, your folder structure would look as follows:

    └── my-store/    ├── home
        └── catalog
  2. Now, let’s run the home app by typing the following commands:
    cd homepnpm run dev
  3. Your app should now be served on port 3000. Verify it by visiting http://localhost:3000 on your browser.
  4. Let’s get rid of the boilerplate code and add simple navigation. Locate and open up the file located at home/pages/index.js and replace everything within the <main></main> tags with the following:
         <main className={styles.main}>       <nav><a href="/">Home</a> | <a href="/catalog">Catalog</a> </nav>
            <h1 className={styles.title}>
              Home:Hello World! 
              </h1>
              <h2>Welcome to my store</h2>
            </main>

    Note that we’ve added basic navigation to navigate between the home and catalog pages. Your home app that is running on localthost:3000 should now look as follows:

Figure 1.5 – Screenshot of the home app with two navigation links for Home and Catalog

Figure 1.5 – Screenshot of the home app with two navigation links for Home and Catalog

  1. Now, let’s move on to the catalog app. Navigate to the index page, located at /catalog/pages/index.js, and again, let’s get rid of the boilerplate code and replace the contents within the <main> tag with the following code:
          <main className={styles.main}>        <nav><a href="/">Home</a> | <a href="/catalog">Catalog</a> </nav>
            <h1 className={styles.title}>
                Catalog:Hello World! 
          </h1>
           <h2>List of Products</h2>
          </main>

    Now, since we already have the home page being served on port 3000, we will run our catalog app on port 3001.

  2. We do this by adding the port flag for the dev command within the scripts section of the catalog/package.json file, as follows:
    "scripts": {    "dev": "next dev -p 3001
    …
    }
  3. Now, running pnpm run dev from within the catalog app should run the catalog app on http://localhost:3001. You can see this in the following screenshot:
Figure 1.6 – Screenshot of the catalog app running on port 3001

Figure 1.6 – Screenshot of the catalog app running on port 3001

The next step is to wire these up such that when the user hits localhost:3000, it directs them to the home app, and when the user hits localhost:3000/catalog, they are redirected to the catalog app. This is to ensure that both apps feel as if they are part of the same app, even though they are running on different ports.

  1. We do this by setting the rewrites rule in the home/next.config.js file, as follows:
    const nextConfig = {  reactStrictMode: true,
      swcMinify: true,
      async rewrites() {
        return [
          {
            source: '/:path*',
            destination: `/:path*`,
          },
          {
            source: '/catalog',
            destination: `http://localhost:3001/catalog`,
          },
          {
            source: '/catalog/:path*',
            destination: `http://localhost:3001/catalog/:path*`,
          },
        ]
      },
    }
    module.exports = nextConfig

    As you can see from the preceding code, we simply tell Next.js that if the source URL is /catalog, then load the app from localhost:3001/catalog.

  2. Before we test it out, there is another small change needed to the catalog app. As you can see, the catalog app will be served on the root of port 3001, but what we would like is for it to be served at :3000/catalog. This is because with the rewrite we did earlier, Next.js will expect the catalog apps and its assets to be available at /catalog/*. We can do this by setting the basePath variable in the catalog/next.config.js file as follows:
    const nextConfig = {  reactStrictMode: true,
      swcMinify: true,
      basePath:'/catalog'
    }
  3. Now, to test that this is working fine, we will run up both of the apps in two different terminal windows by navigating to the home and catalog apps and running the pnpm run dev command.
  4. Open up http://localhost:3000 in your browser and verify that the home app is loaded. Click on the Catalog link and verify that the catalog page does load up at http://localhost:3000/catalog. Notice that the app catalog that’s running individually on port 3001 is sort of “proxied” to load up within a unique URL of the parent/host app. This is one of the key principles of microfrontends, where apps running on different ports and different locations are “stitched” together to make it look like they are a part of the same application.

With that, we come to the end of creating our very first microfrontend with the multi-SPA pattern. We will look at the micro apps pattern in more detail in the upcoming chapters. This pattern meets the majority of the use cases for building microfrontends and checks all the key principles of microfrontends, which we are going to see in the next chapter.

Summary

It’s a wrap for this chapter. We started off by learning how microfrontends (when executed correctly) help teams to continue to release new features at a consistent pace even as the app size and complexity grow. Then, we learned that there are two primary patterns for implementing microfrontends, the multi-SPA pattern and the micro apps pattern. We saw that the multi-SPA pattern is easier to implement and would suit the majority of use cases. The micro apps pattern would be more suitable when different elements of a given page are owned by different scrum teams. Finally, we learned how to build our very own microfrontend application and saw how we can navigate between the two apps while still giving the user the illusion that they are both part of a single app.

In the next chapter, we will look at some of the key principles to strictly adhere to when designing your microfrontend architecture. We will also look at some of the key components of microfrontend and the various ways they can be implemented.

You have been reading a chapter from
Building Micro Frontends with React 18
Published in: Oct 2023 Publisher: Packt ISBN-13: 9781804610961
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime}