In my previous article, I introduced Holocron, a new way of creating Micro Frontends in React. Now, here is the deal…
“How do you assemble all the jigsaw pieces in a Micro Frontend architecture puzzle? the answer is… NOT using Iframes!”
Most of the examples that you find on the internet on how to create a Micro frontend based application mention the usage of iframes, but let’s be honest, Iframes are bad 🙁… And they are by no means the elegant solution that the modern web is used to these days… so my question again is:
Is there a secure way to assemble dozens of React modules on a single page, preserving a small page load, Server Side Rendering and loading just parts of the frontend experience as they are required?
With One App and Holocron, the answer is YES!… all of the above is possible and much more!
Module Composition with Holocron
Holocron modules are loaded in memory and get updated dynamically whenever The One App Server polls the module map that contains the URLs to the Module bundles (e.g. my-module.browser.js
). When a new HTTP request arrives, the One App Server renders one or more modules on the page becoming a single experience to the user. Similar to how components in React work, modules are composable, meaning that a single module can load and reuse other modules.
The Root Module
The first or “top-level” module is called The root module. This is module is the entry point for your application. You can compare it to the top-level component of a React application where all the other components are rendered as children underneath. As well as rendering the children modules, the root module is in charge of the App configuration, like the Content Security Policy, CORS origins, and the Routing.
Children Modules
These modules are the true definition of what micro frontend is in practice. They are self-contained Web Experiences that consist of React Components, Redux Reducers, and Actions as well as Routes. They are independent of other modules, load all the data / make all the API requests they need to render, and can be reused across the application with a configurable API via props.
Let’s Get Coding!
We are going to use the root module that we created in the previous post. If you don’t have one yet, don’t worry just run the generator twice, once for your root module, and once for your child module; then select the correct module type when prompted.
Use the following command to create a boilerplate module using the One App Module Generator:
export NODE_ENV=development
npx -p yo -p @americanexpress/generator-one-app-module -- yo @americanexpress/one-app-module
Follow the steps, give your module the name “child”. Head over to your favorite code editor and open the package.json
file of your root module
and add the relative path to your child module
to the modules array under the one-amex
section.
{
"name": "root-module",
"version": "1.0.0",
"description": "",
"contributors": [],
"scripts": {
"start": "one-app-runner",
"prebuild": "npm run clean",
"build": "bundle-module",
"watch:build": "npm run build -- --watch",
"clean": "rimraf build",
"prepare": "npm run build",
"test:lint": "eslint --ignore-path .gitignore --ext js,jsx,snap .",
"test:unit": "jest",
"test": "npm run test:lint && npm run test:unit"
},
"one-amex": {
"runner": {
"dockerImage": "oneamex/one-app-dev:latest",
"rootModuleName": "root-module",
"modules": [
".",
"../child"
]
}
},
"dependencies": {
"@americanexpress/one-app-router": "^1.0.0",
"holocron": "^1.1.0",
"react": "^16.12.0",
"content-security-policy-builder": "^2.1.0"
},
"devDependencies": {
"@americanexpress/one-app-bundler": "^6.0.0",
"@americanexpress/one-app-runner": "^6.0.0",
"amex-jest-preset-react": "^6.0.0",
"babel-eslint": "^8.2.6",
"babel-preset-amex": "^3.2.0",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.4.4",
"eslint": "^6.8.0",
"eslint-config-amex": "^11.1.0",
"jest": "^25.1.0",
"rimraf": "^3.0.0"
},
"jest": {
"preset": "amex-jest-preset-react"
}
}
The modules
array points to the relative path of other modules in the same project that can be loaded during local development. Add to this array the relative paths of any modules that you wish to load locally.
Composing Modules Using Routes:
The first way of composing Holocron modules is by creating a route that will load the individual module using holocron-module-route.
npm i -S holocron-module-route
Let’s create a new route called child-route
which will load our child module.
In your root module’s childRoutes.jsx
file, add the following:
import React from 'react';
import { Route } from '@americanexpress/one-app-router';
import ModuleRoute from 'holocron-module-route';
const childRoutes = () => [
<Route path="/" />,
<ModuleRoute path="child-route" moduleName="child" />,
];
export default childRoutes;
Next, we need to render the child module in the Root module’s main component. Modules loaded as routes using ModuleRoute
will be passed to the root module’s render method via props.
import React from 'react';
import childRoutes from '../childRoutes';
const RootModule = ({ children }) => (
<div>
<h1>Welcome to One App!</h1>
{ children }
</div>
);
RootModule.childRoutes = childRoutes;
if (!global.BROWSER) {
// eslint-disable-next-line global-require
RootModule.appConfig = require('../appConfig').default;
}
export default RootModule;
After you have made your changes, bundle your root module by running the npm run build
command. You can also use the npm run watch:build
command to listen for changes during development.
To see the results, start the One App Server by running the npm start
command (requires Docker) from the root module and head over to http://localhost:3000/child-route. You should see the contents of your root module as well as the contents of your child module.
Note: The first time that you use
npm start,
it might take a couple of minutes to download the One App Development Image from Docker hub.
Challenge Time!: Make changes to the render method of your child module, keep the One App server running in the background and don’t forget to use npm run build
on your module to bundle the changes.
Holocron ComposeModules
The second method is to load modules as “chunks” within the same page using composeModules
. This method is useful when you want to render multiple modules on the same route.
In the following example, we are going to create a third module, but in this case, instead of loading it on a route using holocron-module-route
, we will let the child
module compose it and load it in its own render method.

Let’s create a new module called grand-child
using the generator. Select “child” as the module type.
*The recommended folder structure is to keep related modules under the same folder so it is easier to add them by the relative path.
holocron-example
├── root-module
├── child
├── grand-child
Add the grand-child
to the modules
array in package.json
of the root-module so it is loaded during development:
"one-amex": {
"runner": {
"dockerImage": "oneamex/one-app-dev:latest",
"rootModuleName": "root-module",
"modules": [
".",
"../child",
"../grand-child"
]
}
}
The first step is to dispatch composeModules
from the module that you want to load the grand-child
module from. composeModules
is an action creator that loads Holocron modules and their data.
Open the main component of the child module Child.jsx,
so we can load the grand-child module that we just created.
import React from 'react';
import { composeModules } from 'holocron';
export const Child = () => (
<div>
<h1>I am the child module!</h1>
</div>
);
export const loadModuleData = ({ store: { dispatch } }) => dispatch(composeModules([
{ name: 'grand-child' },
]));
Child.holocron = {
name: 'child',
loadModuleData,
};
export default Child;
We need to dispatch the composeModules
action creator from the loadModuleData
function. This Holocron function fetches the data required by your module. Lastly, we add the loadModuleData
function to the Holocron module configuration object.
Holocron RenderModule
The last step is to render the grand-child
module in our child
module by using the RenderModule
React component.
Inside the render method of your Child.jsx
module, add the following:
<RenderModule moduleName="grand-child" />
The final version of Child.jsx
should look like this:
import React from 'react';
import { composeModules, RenderModule } from 'holocron';
export const Child = () => (
<div>
<h1>I am the child module!</h1>
<RenderModule moduleName="grand-child" />
</div>
);
export const loadModuleData = ({ store: { dispatch } }) => dispatch(composeModules([
{ name: 'grand-child' },
]));
Child.holocron = {
name: 'child',
loadModuleData,
};
export default Child;
To view the final result, bundle your child module
by running npm run build
and check your changes on http://localhost:3000/child-route.
Ensure your One App Server is still running in the background, if not run
npm start
again from yourroute-module.
You should see your three modules rendering on the page:

Challenge Time! (x2): Add 2 more modules, 1 loaded under the /child-route/module-4
and the last module composed and rendered by the 4th module.
Horray! 🎉
Conclusion
The powerful composition model that makes React so flexible can now be extended to whole frontend experiences encapsulated in Holocron modules. These experiences can be developed, tested, and deployed individually without the need for a server restart. They can also be shared across the application the same way components are shared and reused, reducing code duplication and allowing independent teams to work concurrently on different parts of the application without breaking or interfering with each other’s work.
The source code for this article can be found here: https://github.com/infoxicator/holocron-composition-example