DEV Community

Nader Dabit
Nader Dabit

Posted on

Building Micro Frontends with React, Vue, and Single-spa

Over the past few weeks the there has been a ton of discussion around micro-frontends (some negative, some positive).

There was one tweet that really caught my eye from Joel Denning ‏, the creator of Single SPA:

When I see something new and controversial like this, I always want to try it out myself to see what all of the hype is about and also so I can form my own opinions about the subject.

This lead me down the path to creating a micro-frontend application that rendered two separate React applications along with a single Vue application.

In this tutorial I'll share what I've learned and show you how to build a micro-frontend app consisting of a React and a Vue application.

To view the final code for this application, click here.

Single SPA

The tool we will be using to create our project is Single SPA - A javascript framework for front-end microservices.

Single SPA enables you to use multiple frameworks in a single-page application, allowing you to split code by functionality and have Angular, React, Vue.js, etc. apps all living together.

You may be used to the days of the Create React APP CLI and the Vue CLI. With these tools you can quickly spin up an entire project, complete with webpack configurations, dependencies, and boilerplate ready to go for you.

If you're used to this ease of setup, then this first part may be somewhat jarring. That is because we will be creating everything from scratch, including installing all of the dependencies we need as well as creating the webpack and babel configuration from scratch.

If you are still curious what Single SPA does or why you may want to build using a micro-frontend architecture, check out this video.

Getting Started

The first thing you'll need to do is create a new folder to hold the application and change into the directory:

mkdir single-spa-app

cd single-spa-app

Next, we'll initialize a new package.json file:

npm init -y

Now, this is the fun part. We will install all of the dependencies that we will need for this project. I will split these up into separate steps.

Installing regular dependencies

npm install react react-dom single-spa single-spa-react single-spa-vue vue

Installing babel dependencies

npm install @babel/core @babel/plugin-proposal-object-rest-spread @babel/plugin-syntax-dynamic-import @babel/preset-env @babel/preset-react babel-loader --save-dev

Installing webpack dependencies

npm install webpack webpack-cli webpack-dev-server clean-webpack-plugin css-loader html-loader style-loader vue-loader vue-template-compiler --save-dev

Now, all of the dependencies have been installed and we can create our folder structure.

The main code of our app will live in a src directory. This src directory will hold subfolders for each of our applications. Let's go ahead and create the react and vue application folders within the src folder:

mkdir src src/vue src/react

Now, we can create the configuration for both webpack and babel.

Creating webpack configuration

In the root of the main application, create a webpack.config.js file with the following code:

const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  mode: 'development',
  entry: {
    'single-spa.config': './single-spa.config.js',
  },
  output: {
    publicPath: '/dist/',
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }, {
        test: /\.js$/,
        exclude: [path.resolve(__dirname, 'node_modules')],
        loader: 'babel-loader',
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ],
  },
  node: {
    fs: 'empty'
  },
  resolve: {
    alias: {
      vue: 'vue/dist/vue.js'
    },
    modules: [path.resolve(__dirname, 'node_modules')],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new VueLoaderPlugin()
  ],
  devtool: 'source-map',
  externals: [],
  devServer: {
    historyApiFallback: true
  }
};

Creating babel configuration

In the root of the main application, create a .babelrc file with the following code:

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }],
    ["@babel/preset-react"]
  ],
  "plugins": [
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-object-rest-spread"
  ]
}

Initializing Single-spa

Registering applications is how we tell single-spa when and how to bootstrap, mount, and unmount an application.

In the webpack.config.js file we set the entry point to be single-spa.config.js.

Let's go ahead and create that file in the root of the project and configure it.

single-spa.config.js

import { registerApplication, start } from 'single-spa'

registerApplication(
  'vue', 
  () => import('./src/vue/vue.app.js'),
  () => location.pathname === "/react" ? false : true
);

registerApplication(
  'react',
  () => import('./src/react/main.app.js'),
  () => location.pathname === "/vue"  ? false : true
);

start();

This file is where you register all of the applications that will be part of the main single page app. Each call to registerApplication registers a new application and takes three arguments:

  1. App name
  2. Loading function (what entrypoint to load)
  3. Activity function (logic to tell whether to load the app)

Next, we need to create the code for each of our apps.

React app

In src/react, create the following two files:

touch main.app.js root.component.js

src/react/main.app.js

import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import Home from './root.component.js';

function domElementGetter() {
  return document.getElementById("react")
}

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Home,
  domElementGetter,
})

export const bootstrap = [
  reactLifecycles.bootstrap,
];

export const mount = [
  reactLifecycles.mount,
];

export const unmount = [
  reactLifecycles.unmount,
];

src/react/root.component.js

import React from "react"

const App = () => <h1>Hello from React</h1>

export default App

Vue app

In src/vue, create the following two files:

touch vue.app.js main.vue

src/vue/vue.app.js

import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import Hello from './main.vue'

const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    el: '#vue',
    render: r => r(Hello)
  } 
});

export const bootstrap = [
  vueLifecycles.bootstrap,
];

export const mount = [
  vueLifecycles.mount,
];

export const unmount = [
  vueLifecycles.unmount,
];

src/vue/main.vue

<template>
  <div>
      <h1>Hello from Vue</h1>
  </div>
</template>

Next, create the index.html file in the root of the app:

touch index.html

index.html

<html>
  <body>
    <div id="react"></div>
    <div id="vue"></div>
    <script src="/dist/single-spa.config.js"></script>
  </body>
</html>

Updating Package.json with scripts

To run the app, let's add the start script as well as a build script in package.json:

"scripts": {
  "start": "webpack-dev-server --open",
  "build": "webpack --config webpack.config.js -p"
}

Running the app

To run the app, run the start script:

npm start

Now, you can visit the following URLs:

# renders both apps
http://localhost:8080/

# renders only react
http://localhost:8080/react

# renders only vue
http://localhost:8080/vue

To view the final code for this application, click here.

Conclusion

Overall, setting up this project was fairly painless with the exception of all of the initial boilerplate setup.

I think in the future it would be nice to have some sort of CLI that handles much of the boilerplate and initial project setup.

If you have the need for this type of architecture, Single-spa definitely seems like the most mature way to do it as of today and was really nice to work with.

My Name is Nader Dabit. I am a Developer Advocate at Amazon Web Services working with projects like AWS AppSync and AWS Amplify. I specialize in cross-platform & cloud-enabled application development.

Top comments (41)

Collapse
 
itachiuchiha profile image
Itachi Uchiha • Edited

I like every new approach in the front-end world.

I'm sure we'll see a job advertisement like that;

We're looking for front-end developers. Must have worked with the micro-frontends for +5 years.

Collapse
 
joehughesdev profile image
Joseph Hughes

This isn't a "new" concept. You could definitely have multiple years of experience on this.

Collapse
 
tusharborole profile image
Tushar Borole

hahahah

Collapse
 
seanmclem profile image
Seanmclem • Edited

But why do this? I'm not saying I wouldn't want this, but why would I?

Collapse
 
rhymes profile image
rhymes

If you are a huge company that has to create an app where parts of it are developed by different teams, and different teams that might have different skill sets or preferences.

It's an architectural model that is probably overkill if you're a solo developer or a small team building a regular web app.

Collapse
 
Sloan, the sloth mascot
Comment deleted
 
rhymes profile image
rhymes

If you watch the video @dabit3 linked:

I agree with @rhymes answer, and also check out the video here.

you'll find out it's exactly for apps that have become too big and need to be re-engineered.

Most big companies don't have the luxury to start from scratch.

If you start an app from zero with dozens of developers in different teams, this should be useful as well

Collapse
 
niorad profile image
Antonio Radovcic

I could imagine some special cases like complex realtime-dashboards for stock-traders. Or apps like Spotify, where one team would handle the actual playing of music, and other teams handle playlist curation and social functionality.

Something often not mentioned in discussions: Microfrontends can be split horizontally or vertically.

Horizontally would mean the case described here: Multiple independent apps on one view. I really don't want to see the resulting bundle-size for this approach.

Vertical splitting means you have one app for one view, and separate e.g. by subdomain where login.app.com, news.app.com, editor.app.com each have their own app. DAZN works like this, for example.

Collapse
 
florianrappl profile image
Florian Rappl

Something often not mentioned in discussions: Microfrontends can be split horizontally or vertically.

This is the key point. With microfrontends you get another dimension for splitting / allocating teams, allowing multiple compositions, e.g., real (independent) fullstack teams.

Collapse
 
seanmclem profile image
Seanmclem

Thank you, there are some examples I understand

Collapse
 
dabit3 profile image
Nader Dabit

I agree with @rhymes answer, and also check out the video here.

Collapse
 
lithmage profile image
LithStud

For whatever reason name ticks me off - Single Single Page Application...

Collapse
 
wilmarques profile image
Wiley Marques • Edited

MFE architecture is really interesting.

But the main reason I like it, is the possibility to publish parts of an app independently.
The approach showed here didn't get this result, we have to bundle everything together.

Collapse
 
taviroquai profile image
Marco Afonso

I agree. I don't see this example as independent frontends because ate the end of the day, they are all bundled together. If you make a change in the react frontend, you have to rebuild all together. Doesn't make to much sense.

Collapse
 
grahamsutton profile image
Graham Sutton

This is probably more a trade-off than anything. It seems like the choice is between a) rebuild everything together which de-duplicates dependencies but you lose the ability to release independently or b) build and release everything separately but risk having duplicate dependencies across all bundles.

Collapse
 
vonheikemen profile image
Heiker

I really do wish they had a better name for this pattern. Maybe "Composable apps"? can we make that happen?

The way i see it is like Django apps but for client-side javascript.

Collapse
 
pgangwani profile image
pgangwani

Does really name matter ?
It's pattern to solve specific problem

Collapse
 
vonheikemen profile image
Heiker • Edited

Yes, it does matter. Remember serverless? Tell me that is not a confusing name.

Anyway, "microfrontends" doesn't say anything useful about the problem they are trying to solve. How micro is "micro". And "frontend"? isn't that the whole user interface how can it be micro? The confusion only increases when someone mentions that it can involve multiple frameworks (how can that still be micro?). If we are talking about independent UI pieces, we already have "components" how is this any diferent?

If you hear "composable apps" things change. "App" means that this thing is independent (maybe a whole team build it), "composable" maybe means that is meant to work along side another app or inside of it.

Thread Thread
 
nsnusername profile image
NSN

Would thinking about it in conjunction with "microservices" and the problems they solve help?

Thread Thread
 
vonheikemen profile image
Heiker

It does help. It would turn it into a dangerous game, we can't just put "micro" in front of everything and hope people understand.

Thread Thread
 
gonchub profile image
Gonzalo Beviglia

Replace microfrontend with microservice, and your concern is still valid. Naming is hard:

Anyway, "microservice" doesn't say anything useful about the problem they are trying to solve. How micro is "micro". And "service"? isn't that the whole user system how can it be micro? The confusion only increases when someone mentions that it can involve multiple frameworks (how can that still be micro?). If we are talking about independent api pieces, we already have "request handlers" how is this any diferent?

Collapse
 
solarliner profile image
🇨🇵️ Nathan Graule

As someone who's half in Django code and half in front-end code, this is the description I never knew I needed. This really makes sense to me.

Collapse
 
dvonlehman profile image
David Von Lehman • Edited

This feels like still too much is shared b/t the multiple front-end apps. You could deploy your Vue and React apps as fully standalone (no shared code, configuration, code base, etc.) applications. Then use an upstream load balancer or CDN to route incoming requests to the right origin server based on the URL path pattern. For example all /products/* requests go to the React app and /cart/* requests to the Vue app. The only other consideration is making sure navigation links that cross app boundaries are full page requests rather than client routed links. I assume the AWS Management Console works along these lines, i.e. us-west-2.console.aws.amazon.com/d... is a completely separate app than us-west-2.console.aws.amazon.com/ec2 even though they share a root domain name.

Collapse
 
mrispoli24 profile image
Mike Rispoli

I can think of one great potential use case that I had for micro frontends a few years ago. We had a legacy angular 1 site and google decided to just up and re-write the whole dam framework. As the years wore on it became harder and harder to work with, especially knowing how much better front end libraries and paradigms had gotten. When the time came, we ended up doing a full rebuild ground up to get it off the angular 1 monolith. A pattern like this would have allowed us to gradually move pieces of the site more easily.

Cost wise it may have ended up being roughly the same for the client but in a micro frontends system we probably could have moved a few pages each month over to react rather than do a bit switcheroo after a big multi-month redesign / rebuild. It's a good tool to have in the kit if another situation like this arises.

Collapse
 
leob profile image
leob

I can't really understand (as you are mentioning) that people start attacking each other "ad hominem" about a purely technical/professional topic like this. It must be that people are feeling threatened by this development because they think their expertise (React dev, Vue dev, XYZ dev) might become less valuable. Futile of course because change will always be there and is inevitable.

Collapse
 
florianrappl profile image
Florian Rappl

I think this one confuses microfrontends with "run multiple frameworks".

How is this developed independently when its all in the same codebase?

Even if we factor it out (e.g., into libraries); how it this deployed independently if we need to reference it strongly?

Now let's assume all these things are still dealt with. I can run one app (e.g., React) in the main one, but what if my React app wants to use a fragment of another app? I don't see an immediate solution without creating another "Single SPA" host (which then goes full circle to the first question).

Otherwise great article!

Collapse
 
milky2028 profile image
Tyler

My big questions:

  1. Are these compiling to web components? I know vue cli can natively compile to web components, as can Angular.
  2. What's the bundle size comparison between a Vue/React combo app and just one or the other?
Collapse
 
anikethsaha profile image
anikethsaha

I CAN get the same with react. The main concept of component is this like to work on seperate modules by the seperate teams. I didn't see any guide to handle the server requesting in the single-spa so I guess it is not so "needed" even if a big company ca split up there work with react + react-router to achieve this. I guess next-js is much more helpful than this concept.

This is how I just felt about it. The single-spa team did a good job btw with working with such a tech that no one is ready to use it.

Collapse
 
blas_i profile image
Iñaki Garcia de Blas

I think with this approach you could combine different React versions. Not telling this would be a good idea.

Collapse
 
drsensor profile image
૮༼⚆︿⚆༽つ

Curious about the bundle size and performance. Also, some lighthouse score maybe 🙂

Collapse
 
gonchub profile image
Gonzalo Beviglia

The micro-frontend approach is just a way to architecture big apps/teams. As long as you are serving the same assets, the browser outcome will be the same.

If you opt for a lazy loading approach: you will have to do a small request for the micro-frontend "runtime" (which decides which bundle to load and use) and an additional one for the actual app (the one the runtime decided to load).

If you opt for an eager loading approach: you will have both apps (with React and Vue) as part of your bundle and you will do only one request

You can come up with whatever loading approach you want to mitigate loading times & bundle size. Think of it as glorified code splitting if you want.

Collapse
 
michaelrgalloway profile image
michaelrgalloway

This entire problem is solved by compiling your Vue, React, Angular, etc.. apps to web components. Now you can use your modular mini-app on any site. Agnostic of the framework or even no framework at all.

Collapse
 
leob profile image
leob

But a component is not an "app". Micro frontends is about composing multiple 'apps' (which might need to communicate amongst each other) into one frontend. So compiling into web components might be a useful approach but I don't think it's the whole story.

Collapse
 
theodesp profile image
Theofanis Despoudis • Edited

I believe that the only way this is suitable if your team members are bored using React all the time and they want to use something else like Angular or Vue.js. Just throw them some bones to make them happy 🍖 🐩

Collapse
 
dreamrdeceiver profile image
Abhijith Darshan Ravindra

Hi i have implemented such a poc for my company SAP. Any tips on how to do individual deployments of the micro frontend? Right now it works with local dev env. I implemented this also in iFrame solution. But there's problems with socket connections in safari and SAML single sign on. Any tips would help a lot.