<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Microfrontend - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Microfrontend - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 21 Sep 2026 13:07:51 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/microfrontend/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Micro Frontends in React with Vite and Module Federation ]]>
                </title>
                <description>
                    <![CDATA[ Micro Frontend Architecture has become increasingly popular in recent years, as teams look to re-use parts of their existing applications in new projects rather than rebuilding everything from scratch. Micro frontends also allow large teams to share ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-micro-frontends-in-react-with-vite-and-module-federation/</link>
                <guid isPermaLink="false">68ae1d943a7c9745e83e7797</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ module federation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microfrontend ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Grant Riordan ]]>
                </dc:creator>
                <pubDate>Tue, 26 Aug 2025 20:48:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756231755011/283b9e67-9a09-4241-9d90-701cb075084d.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Micro Frontend Architecture has become increasingly popular in recent years, as teams look to re-use parts of their existing applications in new projects rather than rebuilding everything from scratch.</p>
<p>Micro frontends also allow large teams to share common components – such as headers, footers, and logins – across multiple applications, ensuring consistency and keeping their projects DRY (Don’t Repeat Yourself).</p>
<p>In this article, you will learn:</p>
<ul>
<li><p>How to set up a project and folder structure that implements a Micro Frontend (MFE) Infrastructure</p>
</li>
<li><p>How to use and configure the <code>@originjs/vite-plugin-federation</code> to allow Module Federation (MF) usage with Vite projects.</p>
</li>
<li><p>How to share and consume React components between apps.</p>
</li>
<li><p>How to run and test your app with shared components locally</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-pre-requisites">Pre-Requisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-module-federation">What Is Module Federation?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-module-federation">Benefits of Module Federation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-structure">Project Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-projects">How to Create the Projects</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-dependencies">How to Install Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-the-remote-app">How to Configure the Remote App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-vite-module-federation-plugin">The Vite Module Federation Plugin</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-constraints-on-exported-modules">Key Constraints On Exported Modules</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-remote-components">How to Create the Remote Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-consume-the-remote-components-within-the-host">How to Consume the Remote Components Within the Host</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-typescript-errors">How to Handle TypeScript Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-remotewrappercomponent-to-apptsx">How to Add RemoteWrapperComponent to App.tsx</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-serve-the-remote-app-and-run-your-host">How to Serve the Remote App and Run Your Host</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-true-power-of-micro-frontends">The True Power of Micro Frontends</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-pre-requisites">Pre-Requisites</h2>
<ul>
<li><p>Node installed on your machine – you can get your download of Node <a target="_blank" href="https://nodejs.org/en/download">here</a></p>
</li>
<li><p>Familiarity with JS / TS and React</p>
</li>
<li><p>Familiarity with Command Line / Terminal</p>
</li>
</ul>
<h2 id="heading-what-is-module-federation">What Is Module Federation?</h2>
<p>Before we go any further, let’s talk about Module Federation (MF).</p>
<p>Module Federation is a technique in web development that allows multiple separate builds to work together as a single application. It enables the sharing of code between different, independent applications at runtime, rather than at build time. This means a host application can dynamically load and execute code from a remote application.</p>
<p>At its core, Module Federation uses a <strong>"host"</strong> and <strong>"remote"</strong> architecture. A <strong>host</strong> application is the main app that consumes shared code / components. A <strong>remote</strong> application is the one that exposes code to be consumed.</p>
<p>The remote app specifies <strong>which</strong> parts of its code, or "modules," are available for others to use. The host then references these modules and loads them as needed.</p>
<h2 id="heading-benefits-of-module-federation">Benefits of Module Federation</h2>
<h3 id="heading-independent-deployment">Independent Deployment</h3>
<p>Module Federation lets teams build and deploy their micro-frontends separately. This eliminates the need for full application redeployments, accelerating development and reducing risk.</p>
<h3 id="heading-efficient-code-sharing">Efficient Code Sharing</h3>
<p>MF provides a native way to share code and dependencies between micro-frontends. This prevents code duplication, reducing bloat and ensuring consistency.</p>
<h3 id="heading-performance-gains">Performance Gains</h3>
<p>By sharing dependencies, Module Federation reduces the overall bundle size and improves initial load times, as each micro-frontend doesn't download its own copy of common libraries.</p>
<h3 id="heading-scalability-and-maintainability">Scalability and Maintainability</h3>
<p>MF enables a scalable architecture by breaking down large applications into smaller, manageable micro-frontends. This makes the codebase easier to maintain and allows teams to work independently.</p>
<h3 id="heading-analogy">Analogy</h3>
<p>Imagine building an online store. Traditionally, you’d create the entire site – homepage, product pages, cart, user profile – as one big application. A small cart change would require rebuilding and redeploying everything.</p>
<p>With Micro Frontends and Module Federation, the shopping cart can be its own app, built and maintained by a dedicated team. The main site simply imports it, allowing the cart team to release updates independently, speeding up development and improving focus.</p>
<p>This also works for organisations with multiple sites that need a consistent look. Shared components like a header, footer, or product card can be reused across sites with different purposes, such as hiring vehicles or selling furniture, ensuring visual consistency while keeping functionality unique.</p>
<h2 id="heading-project-structure">Project Structure</h2>
<p>You will need to create two projects:</p>
<ul>
<li><p><strong>host</strong> – this will act as your host application</p>
</li>
<li><p><strong>remote</strong> – this will expose the components you want to share</p>
</li>
</ul>
<h2 id="heading-how-to-create-the-projects">How to Create the Projects</h2>
<p>Run the following commands in your terminal to create your root project folder and your two Vite projects:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># create micro-frontends directory for two vite projects, and navigate to folder</span>
mkdir micro-frontends; <span class="hljs-built_in">cd</span> micro-frontends
</code></pre>
<h3 id="heading-create-a-git-repository-optional">Create a Git Repository (Optional)</h3>
<p>Using the below command you can create a Git repository for source control.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># initiate git repo</span>
git init
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># create host application vite app</span>
npm create vite@latest host-app

<span class="hljs-comment"># once submitted the command, select React and press Enter, </span>
Select a framework:
│  ○ Vanilla
│  ○ Vue
│  ● React
│  ○ Preact
│  ○ Lit
│  ○ Svelte
│  ○ Solid
│  ○ Qwik
│  ○ Angular
│  ○ Marko
│  ○ Others

<span class="hljs-comment"># select Typescript + SWC and again press Enter</span>
Select a variant:
│  ○ TypeScript
│  ● TypeScript + SWC
│  ○ JavaScript
│  ○ JavaScript + SWC
│  ○ React Router v7 
│  ○ TanStack Router
│  ○ RedwoodSDK 
│  ○ RSC
</code></pre>
<p>Once this is done, navigate back to the root project folder (<code>micro-frontends</code>):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># navigate back</span>
<span class="hljs-built_in">cd</span> ../

<span class="hljs-comment"># create remote-app </span>
npm create vite@latest remote-app

<span class="hljs-comment"># follow instructions as before to select React, Typescript + SWC</span>
</code></pre>
<p>You now have your two projects, <code>host-app</code> and <code>remote-app</code>.</p>
<h2 id="heading-how-to-install-dependencies">How to Install Dependencies</h2>
<p>Open the <code>micro-frontends</code> folder in your preferred IDE / Code Editor. In this tutorial I’ll be using VS Code.</p>
<p>Tip: You can open the current folder in VS Code via your terminal using the following command <code>code .</code> if you’ve already added <code>code</code> to you PATH.</p>
<p>Once you’ve opened VS Code, open the terminal and run the following command:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> host-app &amp;&amp; npm install -D @originjs/vite-plugin-federation
</code></pre>
<p>and then run:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ../remote-app &amp;&amp; npm install -D @originjs/vite-plugin-federation
</code></pre>
<h3 id="heading-styling">Styling</h3>
<p>To see styling like my examples, you need to add Tailwind CSS to both your remote and host applications. You can find instructions on how to do so <a target="_blank" href="https://tailwindcss.com/docs/installation/using-vite">here</a></p>
<h2 id="heading-how-to-configure-the-remote-app">How to Configure the Remote App</h2>
<p>In order to be able to utilise modules and components from your remote applications, you need to configure your application to expose those modules, and your host app to consume them.</p>
<p>Use the following configurations in your apps to allow your components to be exposed and consumed – don’t worry about the components just yet, you’ll create them soon.</p>
<h3 id="heading-host-app-viteconfigts">Host App – <code>Vite.config.ts</code></h3>
<p>In the <code>host</code> app, open <code>vite.config.js</code> and add the following configuration:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//host - vote.config.js</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">"@vitejs/plugin-react-swc"</span>;
<span class="hljs-keyword">import</span> federation <span class="hljs-keyword">from</span> <span class="hljs-string">"@originjs/vite-plugin-federation"</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  plugins: [
    react(),
    federation({
      name: <span class="hljs-string">"host_app"</span>,
      remotes: {
        remote_app: <span class="hljs-string">"http://localhost:5001/assets/remoteEntry.js"</span>,
      },
      shared: [<span class="hljs-string">"react"</span>, <span class="hljs-string">"react-dom"</span>],
    }),
  ],
  build: {
    modulePreload: <span class="hljs-literal">false</span>,
    target: <span class="hljs-string">"esnext"</span>,
    minify: <span class="hljs-literal">false</span>,
    cssCodeSplit: <span class="hljs-literal">false</span>,
  },
});
</code></pre>
<h3 id="heading-remote-app-viteconfigts">Remote App – <code>Vite.config.ts</code></h3>
<p>In the <code>remote</code> app, open <code>vite.config.js</code> and add the following configuration:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// remote - vite.config.js</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">"@vitejs/plugin-react-swc"</span>;
<span class="hljs-keyword">import</span> federation <span class="hljs-keyword">from</span> <span class="hljs-string">"@originjs/vite-plugin-federation"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  plugins: [
    react(),
    federation({
      name: <span class="hljs-string">"remote_app"</span>,
      filename: <span class="hljs-string">"remoteEntry.js"</span>,
      exposes: {
        <span class="hljs-string">"./Button"</span>: <span class="hljs-string">"./src/components/Button"</span>,
        <span class="hljs-string">"./Header"</span>: <span class="hljs-string">"./src/components/Header"</span>,
      },
      shared: [<span class="hljs-string">"react"</span>, <span class="hljs-string">"react-dom"</span>],
    }),
  ],
  build: {
    modulePreload: <span class="hljs-literal">false</span>,
    target: <span class="hljs-string">"esnext"</span>,
    minify: <span class="hljs-literal">false</span>,
    cssCodeSplit: <span class="hljs-literal">false</span>,
  },
  preview: {
    port: <span class="hljs-number">5001</span>,
    strictPort: <span class="hljs-literal">true</span>,
    cors: <span class="hljs-literal">true</span>,
  },
});
</code></pre>
<h4 id="heading-explanation-of-vite-configuration">Explanation of Vite Configuration:</h4>
<p>1. <code>plugins</code>: array where you tell Vite which <strong>plugins</strong> to use when it:</p>
<ul>
<li><p>Runs your dev server</p>
</li>
<li><p>Builds your production bundle</p>
</li>
</ul>
<p>Each plugin is basically a little (or big) piece of code that hooks into Vite’s build pipeline to add extra features – for example:</p>
<ul>
<li><p>Adding React JSX/TSX support (<code>@vitejs/plugin-react</code>)</p>
</li>
<li><p>Enabling Module Federation (<code>@originjs/vite-plugin-federation</code>)</p>
</li>
</ul>
<p>2. <code>build</code>: Controls how Vite produces the production build. You don’t need to worry too much about this for the purpose of this tutorial.</p>
<p>3. <code>preview</code>: Controls how the application is served / previewed:</p>
<ul>
<li><p>Useful in microfrontend setups where a fixed port and CORS enabled are needed so other apps can fetch your remote modules.</p>
</li>
<li><p><code>strictPort: true</code> ensures predictable networking – avoids “it works on my machine” issues with random ports.</p>
</li>
</ul>
<h2 id="heading-the-vite-module-federation-plugin">The Vite Module Federation Plugin</h2>
<p>You need to configure your Vite Module Federation plugin to inform it what components are to be consumed and exposed. Let’s take a look at the configured properties:</p>
<ul>
<li><p><code>name</code>: The unique identifier for your remote application in a Module Federation setup. This is the name other applications (hosts) will use when they declare your app as a remote.</p>
</li>
<li><p><code>filename</code>: This is the name of the file that your host will load when it tries to retrieve your exposed modules.</p>
</li>
<li><p><code>exposes</code>: A mapping of public module names → local file paths. This is how you decide which parts of your code are available to be consumed remotely.</p>
</li>
</ul>
<pre><code class="lang-typescript">exposes: {
  <span class="hljs-string">"./Button"</span>: <span class="hljs-string">"./src/components/Button"</span>,
  <span class="hljs-string">"./Header"</span>: <span class="hljs-string">"./src/components/Header"</span>
}
</code></pre>
<p>The <strong>key</strong> (<code>"./Button"</code>) is the <strong>public module name</strong> – the name that other apps (the host) will use when importing the module from your remote.</p>
<h4 id="heading-how-it-works">How It Works:</h4>
<ul>
<li><p><strong>Key</strong> (<code>"./Button"</code>) is the exposed identifier other apps can request.</p>
</li>
<li><p><strong>Value</strong> (<code>"./src/components/Button"</code>) is the actual file path inside your project.</p>
</li>
</ul>
<p>For example, if your remote’s <code>name</code> is <code>"remote_app"</code>, the host can import like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"remote_app/Button"</span>;
</code></pre>
<p>Under the hood, <code>"remote_app"</code> matches the <strong>remote’s</strong> <code>name</code> in its <code>federation()</code> config and <code>Button</code> matches the <code>"./Button"</code> key in <code>exposes</code>.</p>
<ul>
<li><p><code>shared</code>: dependencies that should be <strong>shared</strong> between the host and remote. It avoids shipping duplicate copies of large libraries (like React), ensuring the host and remote use the same instance – you can read more <a target="_blank" href="https://module-federation.io/configure/shared">here</a> about the possible configurations.</p>
</li>
<li><p><code>remotes</code>: A mapping of remote app names → the URL to their <code>remoteEntry.js</code> file. This tells the host where to fetch the exposed modules at runtime.</p>
</li>
</ul>
<pre><code class="lang-typescript">remotes: { remote_app: <span class="hljs-string">"http://localhost:5001/assets/remoteEntry.js"</span> }
</code></pre>
<p>The key <code>remote_app</code> must match the <code>name</code> in the remote’s config.</p>
<p>The value is the full URL to the remote’s entry file (served in dev or deployed in prod). Remember we set <code>strictPort:true</code> earlier – this is why. We need to ensure we’re pointing at the correct domain &amp; port.</p>
<h2 id="heading-key-constraints-on-exported-modules">Key Constraints On Exported Modules</h2>
<h3 id="heading-naming-constraints-and-rules">Naming constraints and rules</h3>
<p><strong>The key in</strong> <code>exposes</code> (<code>"./Button"</code>):</p>
<ul>
<li><p>Must start with <code>./</code> (per Module Federation spec).</p>
</li>
<li><p>Must be unique within the remote.</p>
</li>
<li><p>Is case-sensitive.</p>
</li>
<li><p>This is the <strong>public module path</strong> the host will request.</p>
</li>
<li><p>Doesn’t have to match the filename, but matching is a good convention for ease of reading</p>
</li>
</ul>
<p><strong>The file you point to (</strong><code>"./src/components/Button"</code>):</p>
<ul>
<li><p>Can export default, named exports, or both.</p>
</li>
<li><p>The host can import default or named exports, same as any ES module:</p>
<pre><code class="lang-typescript">  <span class="hljs-comment">// Default export</span>
  <span class="hljs-keyword">import</span> MyButton <span class="hljs-keyword">from</span> <span class="hljs-string">'remote_app/Button'</span>;

  <span class="hljs-comment">// Named export</span>
  <span class="hljs-keyword">import</span> { SpecialButton } <span class="hljs-keyword">from</span> <span class="hljs-string">'remote_app/Button'</span>;
</code></pre>
</li>
</ul>
<p><strong>The import name:</strong></p>
<ul>
<li><p>Completely up to the host developer when importing a <strong>default</strong> export.</p>
</li>
<li><p>Must match exactly for <strong>named</strong> exports.</p>
</li>
</ul>
<h2 id="heading-how-to-create-the-remote-components">How to Create the Remote Components</h2>
<p>Ok, so you’ve created your project structure, and you’ve setup your <code>vite.config.ts</code> file to allow for exposing and consuming your shared assets. Next you will create the remote components.</p>
<h3 id="heading-button-component">Button Component</h3>
<p>Let’s say you want to create a button component which will be shared across all your host applications, as you want to keep consistency. You can do this as below:</p>
<p>Navigate to the <code>remote-app</code> folder and create a new file called <code>Button.tsx</code> in <code>src/components</code> this will ensure it matches the configured federation plugin.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// remote - ./src/components/Button.tsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> ButtonProps {
  text: <span class="hljs-built_in">string</span>;
  onClick?: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> Button: React.FC&lt;ButtonProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ text, onClick }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;button
      onClick={onClick}
      className=<span class="hljs-string">"px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 hover:cursor-pointer"</span>
    &gt;
      {text}
    &lt;/button&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Button;
</code></pre>
<p>You now have a re-usable <code>Button</code> component which has some base styling but allows for configuration of what the button does by passing in a <code>onClick()</code> argument.</p>
<h3 id="heading-header-component">Header Component</h3>
<p>Sticking with the theme of consistency, you want to create a <code>&lt;header/&gt;</code> component which you can use on all your organisation’s websites, ensuring a themed appearance on all applications.</p>
<p>Like before, create a <code>Header.tsx</code> file within <code>src/components/</code>, and paste in the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// remote - .src/components/Header.tsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Header: React.FC = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;header className=<span class="hljs-string">"bg-gray-800 text-white p-4"</span>&gt;
      &lt;h1 className=<span class="hljs-string">"text-2xl"</span>&gt;Remote App Header&lt;/h1&gt;
      &lt;p className=<span class="hljs-string">"text-white"</span>&gt;Hi, Grant&lt;/p&gt;
    &lt;/header&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Header;
</code></pre>
<p>I’ve kept it simple, as this tutorial is for proof of concept purposes rather than aesthetic / real-world components.</p>
<h2 id="heading-how-to-consume-the-remote-components-within-the-host">How to Consume the Remote Components Within the Host</h2>
<p>You have your remote components created, so next you need to get them into your host application and begin using them. This is quite simple now that you’ve already setup your <code>vite.config.ts</code>.</p>
<p>You <strong>could</strong> import the components into your <code>App.tsx</code>, but this is not best practice as it can bloat your App.tsx (entry point component). I’ve opted to create a <code>RemoteWrapperComponent</code> which pulls in the remote components and handles the business logic.</p>
<p><code>RemoteComponentWrapper</code><strong>:</strong></p>
<p>Create a file called <code>RemoteComponentWrapper.tsx</code> in <code>src/components</code>, pasting the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// host - ./src/components/RemoteComponentWrapper.tsx</span>
<span class="hljs-keyword">import</span> React, { Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> RemoteHeader = React.lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">"remote_app/Header"</span>));
<span class="hljs-keyword">const</span> RemoteButton = React.lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">"remote_app/Button"</span>));

<span class="hljs-keyword">const</span> LoadingSpinner = <span class="hljs-function">() =&gt;</span> (
  &lt;div className=<span class="hljs-string">"flex justify-center p-4"</span>&gt;
    &lt;div className=<span class="hljs-string">"animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"</span>&gt;&lt;/div&gt;
  &lt;/div&gt;
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> RemoteComponentWrapper = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"p-4"</span>&gt;
      &lt;Suspense fallback={&lt;LoadingSpinner /&gt;}&gt;
        &lt;RemoteHeader /&gt;
      &lt;/Suspense&gt;

      &lt;div className=<span class="hljs-string">"mt-4"</span>&gt;
        &lt;Suspense fallback={&lt;LoadingSpinner /&gt;}&gt;
          &lt;RemoteButton
            text=<span class="hljs-string">"Remote Button"</span>
            onClick={<span class="hljs-function">() =&gt;</span>
              alert(
                <span class="hljs-string">"Well done you've imported the MF remote component successfully"</span>
              )
            }
          /&gt;
        &lt;/Suspense&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<p>This component acts as a wrapper, handling some very simple business logic such as loading your remote components, handling a loading spinner whilst waiting for the remote, and passing your <code>onClick</code> event to the remote button.</p>
<h3 id="heading-why-use-reactlazy">Why Use <code>React.lazy()</code>?</h3>
<p>The import with <code>React.lazy</code> isn’t required for Module Federation components – it’s more of a best practice for React apps when the remote module is:</p>
<ul>
<li><p>Loaded asynchronously at runtime, which Module Federation remotes almost always are</p>
</li>
<li><p>You want React to handle the loading state and code-splitting gracefully – shown using the <code>Suspense</code> component</p>
</li>
</ul>
<p><code>React.lazy</code> + <code>&lt;Suspense&gt;</code> gives React a built-in way to pause rendering until the component is ready. Without it, you’d need manual loading state handling.</p>
<p>It also keeps your components looking “normal.” With React.Lazy, <code>&lt;RemoteHeader/&gt;</code> is just another component in your JSX.</p>
<p>Without it, you’d need something like:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [Header, setHeader] = useState(<span class="hljs-literal">null</span>);

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">import</span>(<span class="hljs-string">"remote_app/Header"</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> setHeader(<span class="hljs-function">() =&gt;</span> m.default));
}, []);

<span class="hljs-keyword">return</span> Header ? &lt;Header /&gt; : &lt;LoadingSpinner /&gt;;
</code></pre>
<p>…which is messier and repeats for every remote component.</p>
<h2 id="heading-how-to-handle-typescript-errors">How to Handle TypeScript Errors</h2>
<p>Inside your <code>RemoteWrapperComponent</code> you’re going to see the following error on your <code>Button</code> and <code>Header</code> imports.</p>
<blockquote>
<p>Cannot find module 'remote_app/Button' or its corresponding type declarations.ts (2307)</p>
</blockquote>
<p>You get this error because the remote modules are not defined with types, so both your remote and your host doesn’t know what this imported component is, nor does it know its structure (a key part to TypeScript development).</p>
<p>To fix this you will need to provide your host app with custom types.</p>
<h3 id="heading-add-a-type-declaration-file">Add a Type Declaration File</h3>
<p>A type declaration file (if you’re unaware of them) has a <code>.d.ts</code> suffix.</p>
<p>Within your host app, create a file in <code>src/types</code> called <code>remote-app.d.ts</code>. Naming the file in this way lets us know the declarations within are related to the <em>remote-app</em>. This is useful especially when consuming multiple remotes.</p>
<p>Copy and paste the following declarations into your <code>remote-app.d.ts</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// host - ./src/types/remote.d.ts</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> "remote_app/Button" {
  <span class="hljs-keyword">const</span> Button: React.FC&lt;{
    text: <span class="hljs-built_in">string</span>;
    onClick?: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
  }&gt;;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Button;
}

<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> "remote_app/Header" {
  <span class="hljs-keyword">const</span> Header: React.FC;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Header;
}
</code></pre>
<p>Now if you return to your <code>RemoteWrapperComponent</code> your errors should be gone. If they aren’t, restart your IDE (in VS Code you can open your command palette and select <code>Restart Typescript Server</code>).</p>
<h2 id="heading-how-to-add-remotewrappercomponent-to-apptsx">How to Add <code>RemoteWrapperComponent</code> to App.tsx</h2>
<p>Import the <code>RemoteWrapperComponent</code> into App.tsx.</p>
<p>I’ve removed all the boilerplate code and replaced with some basic styling to allow us to easily see what is the host, and what are the remote components.</p>
<p>Copy and paste the following code into your host <code>App.tsx</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// host - ./src/App.tsx</span>
<span class="hljs-keyword">import</span> viteLogo <span class="hljs-keyword">from</span> <span class="hljs-string">"/vite.svg"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> { RemoteComponentWrapper } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/RemoteComponentWrapper"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;div className=<span class="hljs-string">"px-6 border-2"</span>&gt;
        &lt;div className=<span class="hljs-string">"flex justify-center items-center"</span>&gt;
          &lt;img src={viteLogo} alt=<span class="hljs-string">"Example"</span> /&gt;
        &lt;/div&gt;
        &lt;h1 className=<span class="hljs-string">"text-2xl"</span>&gt;Host Application&lt;/h1&gt;
        &lt;p&gt;
          {<span class="hljs-string">" "</span>}
          Welcome to the Host application, below are the components pulled <span class="hljs-keyword">from</span>
          the remote application
        &lt;/p&gt;
        &lt;RemoteComponentWrapper /&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-how-to-serve-the-remote-app-and-run-your-host">How to Serve the Remote App and Run Your Host</h2>
<p>Due to how Vite works, you need to build the application before you preview / serve the application.</p>
<p>Make sure that your <code>package.json</code> file’s scripts block looks like this:</p>
<pre><code class="lang-json"># remote-app
<span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc -b &amp;&amp; vite build"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint ."</span>,
    <span class="hljs-attr">"preview"</span>: <span class="hljs-string">"vite preview --port 5001 --strictPort"</span>,
    <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"npm run build &amp;&amp; npm run preview"</span>
  },

# host-app
<span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc -b &amp;&amp; vite build"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint ."</span>,
    <span class="hljs-attr">"preview"</span>: <span class="hljs-string">"vite preview --port 5000 --strictPort"</span>,
    <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"npm run build &amp;&amp; npm run preview"</span>
  },
</code></pre>
<p>In your terminal run:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ./remote-app

<span class="hljs-comment"># run a build, and run / load the app on port 5000</span>
npm run serve
</code></pre>
<p>You should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755207471886/c64b29ef-3f95-44e5-903b-896f7a3a36fc.png" alt="screenshot showing output from terminal, port 5001 running remote app" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now you can run the host application by doing the same:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># change to the host-app in another terminal </span>
<span class="hljs-built_in">cd</span> ../host-app

<span class="hljs-comment"># build and run the application</span>
npm run serve
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755207573717/77ab133f-ac2e-4f51-8152-3de7200c067c.png" alt="screenshot showing the host-app running on port 5000" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If you open the <code>localhost:5000</code> you should now see your host application with the remote components:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756078239732/775ab6ec-4566-44cd-ac85-66c37a9001ca.png" alt="image: shows the finished host app consuming the remote components" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Next, if you click the button, you can see that it shows the message you configured from within the <code>RemoteWrapperComponent</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756078270491/65a72ab8-1cfa-4298-be02-68d0528fd50f.png" alt="image: shows alert modal with provided message after clicking remote button" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-the-true-power-of-micro-frontends">The True Power of Micro Frontends</h2>
<p>The true power of micro frontends lies in their ability to update the remote components, without the need to rebuild the host. To fully demonstrate this, keep the host running, and update the <code>Button</code> component on your <code>remote-app</code>.</p>
<p>Let’s update the remote components. Use the below code to update both the <code>Button</code> and <code>Header</code> components:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// remote - ./src/components/Header.tsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Header: React.FC = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;header className=<span class="hljs-string">"bg-gray-800 text-white p-4"</span>&gt;
      &lt;h1 className=<span class="hljs-string">"text-2xl"</span>&gt;Updated Remote App Header&lt;/h1&gt;
      &lt;p className=<span class="hljs-string">"text-white"</span>&gt;Hi, Grant&lt;/p&gt;
    &lt;/header&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Header;

<span class="hljs-comment">// remote - ./src/components/Button.tsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> ButtonProps {
  text: <span class="hljs-built_in">string</span>;
  onClick?: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> Button: React.FC&lt;ButtonProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ text, onClick }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;button
      onClick={onClick}
      className=<span class="hljs-string">"px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 hover:cursor-pointer"</span>
    &gt;
      {text}
    &lt;/button&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Button;
</code></pre>
<p>Once you’ve updated the remote components, run the following command in your <code>remote-app</code> folder:</p>
<pre><code class="lang-bash">npm run serve
</code></pre>
<p>Next, refresh your host app in the browser and you will see the updated app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756078980661/02ba0655-a18c-460c-939b-6fa95b4149b7.png" alt="image: shows host application with updated remote compnents" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The update to the remote components is visible immediately, without restarting or rebuilding the host app. This highlights a key benefit of micro frontends: shared components are fetched from their own server via the <code>remote.js</code> file, enabling independent updates.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>You've successfully built and deployed a micro frontend architecture – congrats! This basic implementation demonstrates the true power of Module Federation and the ability to update shared components without needing to rebuild and redeploy the entire host application.</p>
<p>This independence can dramatically accelerate development cycles and empower teams to work more autonomously.</p>
<p>I hope you’ve learned something from this article, and as always for more tutorials and discussions, connect with me on <a target="_blank" href="https://x.com/grantdotdev">twitter/x</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Microfrontends Work: From iframes to Module Federation ]]>
                </title>
                <description>
                    <![CDATA[ Microfrontends are transforming how teams build and deploy frontend applications at scale. This tutorial explores the architectural landscape, from traditional approaches to modern Module Federation implementations. By the end, you'll be equipped to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-microfrontends-work-iframes-to-module-federation/</link>
                <guid isPermaLink="false">6839c4a906e067fe415d54b4</guid>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microfrontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rahul gupta ]]>
                </dc:creator>
                <pubDate>Fri, 30 May 2025 14:46:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748613557891/39037981-d514-4f26-8a48-be0cdd9ca29b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Microfrontends are transforming how teams build and deploy frontend applications at scale. This tutorial explores the architectural landscape, from traditional approaches to modern Module Federation implementations.</p>
<p>By the end, you'll be equipped to evaluate whether microfrontends are the right solution for your team's specific needs.</p>
<h3 id="heading-ill-cover-the-following"><strong>I’ll cover the following:</strong></h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-microfrontends">What are Microfrontends?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-traditional-microfrontend-patterns">Traditional Microfrontend Patterns</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-server-side-composition">Server-Side Composition</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-iframes">iframes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-build-time-integration-packages">Build Time Integration – Packages</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-modern-microfrontend-patterns">Modern Microfrontend Patterns</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-module-federation">Module Federation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-single-spa">Single SPA</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-detailed-comparison">Detailed Comparison</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tradeoffs-and-challenges-with-module-federation">Tradeoffs and Challenges with Module Federation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-setup-complexity">Setup Complexity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-runtime-challenges">Runtime Challenges</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-operational-concerns">Operational Concerns</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusions">Conclusions</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-whats-next">What’s next?</a></li>
</ul>
</li>
</ul>
<h2 id="heading-what-are-microfrontends">What are Microfrontends?</h2>
<p>If you've heard about microservices on the backend, microfrontends represent a similar approach in the frontend world, with many of the same benefits.</p>
<p>Your team might adopt a microfrontend approach to enable team autonomy, reduce deployment risks, and scale development across multiple teams. Each team owns its technology stack, deployment cadence, and workflows. Yet they still deliver a single, cohesive user interface.</p>
<p>The overall idea is to move away from a big monolithic UI to decoupled UI codebases that can be owned, managed, and deployed by separate teams independently.</p>
<p>The simplest way to think about Microfrontends is the following:</p>
<blockquote>
<p>Integrate one piece of UI into another</p>
</blockquote>
<p>What can this <strong>piece</strong> of UI be, you may ask? Here are some examples:</p>
<ul>
<li><p><strong>Pages</strong> – parts of a website owned by specific teams. For example, the Auth team may own login/signup pages, whereas the engagement team may own the marketing pages, and so on.</p>
</li>
<li><p><strong>Components</strong> – Components like header and footer are good candidates for a microfrontend approach as well. They’re relatively static but need to stay consistent across the website and may integrate with teams who own different sets of pages.</p>
</li>
<li><p><strong>Widgets</strong> – A recommendation widget may be owned by a recommendations team, for example, and it can be integrated into different parts of the page based on the context. This is different from a static component, as given the context, the recommendation widget may also fetch relevant data via APIs (also owned by the recommendations teams).</p>
</li>
</ul>
<h2 id="heading-traditional-microfrontend-patterns">Traditional Microfrontend Patterns</h2>
<p>After reading the definition of a microfrontend, you might be thinking, oh, wait, who builds UI with a big monolith these days anyway (except giants like Google)? If that’s the case, your team is most likely using one of these traditional approaches to building Microfrontends:</p>
<h3 id="heading-server-side-composition"><strong>Server-Side Composition</strong></h3>
<p>This is the most common approach I've encountered across various organisations. The idea is to split your website based on route patterns or pages. For example, you might route users to the accounts team for any routes starting with <code>/account/*</code> (<code>/account/login</code> or <code>/account/signup</code> may fall under this pattern). Or you may have a similar route prefix for other parts of your web app, like <code>/blog/*</code> for the marketing section of your app.</p>
<p>This is typically implemented at the reverse proxy layer (such as using NGINX), which routes traffic to the appropriate downstream UI service based on the path matching.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747496226912/fda979cd-c95c-4d48-a7dc-87956672b24d.png" alt="Diagram showing a reverse proxy setup with nginx. The proxy routes `/blog/*` requests to the Marketing UI and `/account/*` requests to the Accounts UI." class="image--center mx-auto" width="1416" height="638" loading="lazy"></p>
<h3 id="heading-iframes"><strong>iframes</strong></h3>
<p>Another common approach is using iframes, though this method has significant limitations.</p>
<p>Unlike server-side composition, which operates at the page level, iframes can integrate as widgets within pages. Using iframes, you can load another website as a part of the website you want to integrate it within using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe">&lt;iframe&gt;</a> tag.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747496936962/7c24a43a-80d4-45f4-a2df-3de0e0e0bc1c.png" alt="Diagram illustrating iframe integration, showing website.com/blog embedding a 'Widget' using an iframe with source 'website.com/widget'." class="image--center mx-auto" width="1560" height="1043" loading="lazy"></p>
<p>Some examples of this approach, which you may have seen, are websites that integrate Twitter feeds, Google Maps, and so on. Although these are examples of external widget integrations with iframes, companies may integrate certain widgets that are powered through iframes.</p>
<h3 id="heading-build-time-integration-packages"><strong>Build Time Integration – Packages</strong></h3>
<p>This approach involves publishing components as a UI library that other applications can integrate.</p>
<p>This is useful if you want to integrate full-blown apps with multiple pages, widgets, or static components like headers and footers, where this approach is pretty common.</p>
<p>Typically, this approach means that one team publishes their components as a package, while other teams integrate a specific version of this package.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747497485861/70385841-28c6-441e-ae4d-c977b3563ecf.png" alt="Diagram illustrating build-time integration via packages, showing website.com/blog integrating a 'Widget' component, version 1.0.0, fetched from a company's NPM registry." class="image--center mx-auto" width="2318" height="1151" loading="lazy"></p>
<p>In this example, it’s important to note that the Widget component is pulled in during the dependency install phase of the app. The web app can utilise this widget like its own component, which gets built together as one module and shipped to the users.</p>
<h2 id="heading-modern-microfrontend-patterns">Modern Microfrontend Patterns</h2>
<h3 id="heading-module-federation">Module Federation</h3>
<p>Module Federation enables you to integrate remote UI pieces within a host application at runtime. These pieces can be full pages, widgets, or components.</p>
<p>Module Federation originated as a <a target="_blank" href="https://webpack.js.org/concepts/module-federation/">Webpack 5 feature</a>, extending the bundler's capabilities to load JavaScript code from remote sources at runtime.</p>
<p><a target="_blank" href="https://module-federation.io/">Module Federation 2.0</a> is the evolution/improvement of the original Webpack 5 feature, with implementations available for other popular bundlers like RSPack and Vite as well.</p>
<p>Even if you’re using Webpack 5, I would recommend using Module Federation 2.0 as it takes care of some common gotchas that exist in the original Webpack 5 implementation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748011963774/fae404c6-efc9-4e0f-8667-4427dbcdfc0f.png" alt="Diagram illustrating Module Federation, showing a host application at website.com/blog loading a 'Widget' component at runtime from a remote application at Recommendation.com via 'remotes: recommendation/remoteEntry'." class="image--center mx-auto" width="2501" height="1144" loading="lazy"></p>
<p>Let’s take an example to understand some of the common pieces of Module Federation.</p>
<p>Imagine that we’ve a blog application, owned by the Content Team &amp; a Widget, which is owned by the Recommendations team.</p>
<p>Now, let’s say the content team wants to integrate a recommendation widget within their application. Assume these teams have separate codebases hosted on different domains. The content team is on <code>website.com</code> &amp; the recommendations team is on <code>recommendation.com</code></p>
<p>Here’s how you can achieve this MFE integration via Module Federation:</p>
<h4 id="heading-remote"><strong>Remote</strong></h4>
<p>Responsible for exposing JavaScript files as remote (for example, utilities, components, and so on).</p>
<p>In our example, it would be the Recommendation’s team acting as a remote &amp; would require a configuration to ‘expose’ the Widget.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> ModuleFederationPlugin({
  name: <span class="hljs-string">'recommendation'</span>,
  exposes: {
    <span class="hljs-string">'./Widget'</span>: <span class="hljs-string">'./src/Widget.js'</span>,
  }
})
</code></pre>
<h4 id="heading-remote-entry"><strong>Remote Entry</strong></h4>
<p>Remote entry is the URL for the entry point for a remote. A remote may expose multiple JavaScript files, &amp; remoteEntry file would be aware of all of them.</p>
<p>Module Federation by default hosts the remote entry file at the root. In our example, recommendation teams might host their remote entry on <code>https://recommendation.com/remoteEntry.js</code></p>
<h4 id="heading-host"><strong>Host</strong></h4>
<p>An independent website that consumes JavaScript from one or more remotes via <strong>Remote Entry.</strong> Think of remote entry as a namespace for your app under which it can export multiple things like components, utils, and so on, as exposed by a particular remote.</p>
<p>In our example, the Content Team would act as a Host &amp; they’ll define the recommendation team’s remote entry within remotes configuration.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> ModuleFederationPlugin({
  name: <span class="hljs-string">'content-blog'</span>,
  remotes: {
    <span class="hljs-string">"recommendation"</span>: <span class="hljs-string">'recommendation@https://recommendation.com/remoteEntry.js'</span>,
  },
  <span class="hljs-comment">// ... other configs</span>
})
</code></pre>
<h4 id="heading-shared"><strong>Shared</strong></h4>
<p>Both hosts and remote can specify dependencies as SemVer that are automatically negotiated and shared during runtime. These can include common framework dependencies, such as React, which may require being a singleton, or other vendor libraries that can be potentially shared.</p>
<p>Having the right shared configuration ensures that the client does not download libraries or code that is already available on the host when fetching UI pieces from a remote location, which is key for optimal performance when integrating Module Federation.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> deps = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./package.json"</span>).dependencies;

<span class="hljs-keyword">new</span> ModuleFederationPlugin({
  shared: {
    ...deps,
    react: {
      singleton: <span class="hljs-literal">true</span>,
      requiredVersion: deps.react,
    }
  },
  <span class="hljs-comment">// ... other configs</span>
})
</code></pre>
<h4 id="heading-imports-and-usage"><strong>Imports and Usage</strong></h4>
<p>Module Federation integration lets you use imports as if those JS files were available locally. Module Federation does all the stitching behind the scenes at runtime, in terms of fetching the remote entry and appropriate dependencies to make it available when you use it.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Import is of the format - &lt;remote&gt;/&lt;expose-from-remote&gt;</span>
<span class="hljs-keyword">import</span> Widget <span class="hljs-keyword">from</span> <span class="hljs-string">'recommendation/Widget'</span>;

<span class="hljs-comment">// Render somewhere, making sure to handle loading via Suspense</span>
<span class="hljs-comment">// &amp; errors via error boundary in React</span>
&lt;ErrorBoundary&gt;
  &lt;Suspense fallback={&lt;Loading /&gt;}
    &lt;Widget /&gt;
  &lt;/Suspense&gt;
&lt;/ErrorBoundary&gt;
</code></pre>
<p>In a nutshell, the module federation concept is this simple —</p>
<blockquote>
<p>Fetching JS code (components, utils, and so on) from a remote server at runtime and still being able to share dependencies and be performant while doing so.</p>
</blockquote>
<h3 id="heading-single-spa">Single SPA</h3>
<p>When you look up microfrontends, <a target="_blank" href="https://single-spa.js.org">Single SPA</a> often appears as a popular solution. But its primary use case is quite specific: integrating components across multiple frameworks (for example, React + Angular + Vue in the same application). Here's how it works in practice:</p>
<p>Single SPA acts as a JavaScript router that mounts and unmounts entire applications based on URL routes. Each "single-spa application" is a framework-specific app that gets loaded when its route becomes active.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Register applications with Single SPA</span>
registerApplication({
 name: <span class="hljs-string">'@mycompany/react-app'</span>,
 app: <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">'@mycompany/react-app'</span>),
 activeWhen: [<span class="hljs-string">'/react-app'</span>]
});

registerApplication({
 name: <span class="hljs-string">'@mycompany/angular-app'</span>, 
 app: <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">'@mycompany/angular-app'</span>),
 activeWhen: [<span class="hljs-string">'/angular-app'</span>]
});
</code></pre>
<p>Single SPA handles the "orchestration" part – deciding which app should be active and managing their lifecycles. It doesn't solve the "how do I load remote code" problem – you still need to pair it with one of the approaches we've discussed (Module Federation, build-time packages, and so on).</p>
<p>If your applications use the same framework (like all React), you can skip Single SPA entirely and use Module Federation directly. Single SPA adds complexity that's only justified when you truly need multi-framework integration.</p>
<h2 id="heading-detailed-comparison">Detailed Comparison</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Criteria</strong></td><td><strong>Module Federation</strong></td><td><strong>Server-side composition</strong></td><td><strong>iframe</strong></td><td><strong>Build time integration (package)</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Independent deployments</strong></td><td>💚 Microfrontends are loaded at runtime on the client. This means that teams can do independent deployments and make changes that reflect immediately.</td><td>💚 Deployments stay independent, as each route pattern points to an individual app’s independent deployments</td><td>💚 Since iframes are also loaded at runtime, the deployments can be independent.</td><td>💔 Deployments are coupled from the host application. A change in the package would require publishing a new version and bumping it up in the host app.</td></tr>
<tr>
<td><strong>Performance</strong></td><td>💚 Enables shared dependencies and optimised loading, maintaining SPA performance.</td><td>💔 Requires full page reloads when navigating between applications, losing SPA benefits.</td><td>💔 Completely isolated and loads all the dependencies of the website in an iframe, which means a slower overall page load.</td><td>💚 Possible to share package dependencies to a certain extent by <a target="_blank" href="https://yarnpkg.com/cli/dedupe">deduping</a> dependencies when integrating a package, but it requires appropriate dev tooling. Otherwise, duplicate dependencies may sneak in for the same package.</td></tr>
<tr>
<td><strong>Scalability &amp; maintenance</strong></td><td>💚 Works well at scale. A page can be completely composed of federated components, with the smallest of the building blocks being pulled in from different remotes.</td><td>💔 Usually requires duplicating things like header/footer, to make it ‘look like’ the user is in the same app, but is being served by two different servers/codebases. The approach is limited to route-based segregation of apps’ entry points – so, a granular integration isn’t possible.</td><td>💔 Typically good to power whole pages and not portions of pages, can really slow down the app at scale and may encounter issues when optimising the app for SEO or building dynamic responsive layouts.</td><td>💔 Requires maintenance of package publishing, upgrades and version conflicts at scale. This can be simplified to some extent by CI tooling, but developers would still require significant effort in bumping the versions, verifying the impact from a feature/performance standpoint.</td></tr>
<tr>
<td><strong>Setup effort</strong></td><td>💔 Might be high depending on how your app is being built currently. A deeper level of understanding of your build tool may be required to make your desired integration work, or when you face issues. This is covered in detail in the next section.</td><td>💚 Simpler to implement, as there is no coupling apart from the reverse proxy layer, which is responsible for redirecting the traffic to the appropriate service.</td><td>💔 Easier to integrate but requires handling a lot of edge cases, which can take significant time. Some examples are communication between iframe/host app, layout issues, rendering beyond the boundaries of iframe (for example, toasts), cross-domain iframe restrictions and ensuring security, impact on SEO and a11y.</td><td>💔 Requires significant effort to stabilise the development pipeline for publishing a new package, maintaining a changeset, bumping a new version, and resolving version conflicts. Every change requires making sure there is no unintended impact on transitive dependencies of the host app due to the package bump.</td></tr>
<tr>
<td><strong>Authentication &amp; Authorisation</strong></td><td>💚 Depending on your app’s setup, module federated components can call the remote app’s server for any data fetching needs. Might require handling CORS if your remote is on a different domain than the host, and for the browser to send authentication cookies for such requests.</td><td>💚 Each app can independently integrate with a central authentication service.</td><td>💔 It can be challenging for iframes to access the parent website’s browser details like auth cookies, and so on. It also may require some trickery to get the authentication to work, especially if the iframe URL is on a different domain/subdomain than the application.</td><td>💚 Package components can choose to call APIs via a proxy API within the host application or directly integrate with an independent service’s endpoints.</td></tr>
<tr>
<td><strong>Devloop</strong></td><td>💚 Mature devloop with Module Federation 2.0, you can view source maps with hot reloading across these apps. Overall a seamless integration out of the box. You can also point to any federated endpoint from local to be able to integrate and verify end-to-end integration.</td><td>💔 Requires setting up both services and a reverse proxy locally to verify integration touchpoints, which may be non-trivial.</td><td>💔 Local testing doesn’t accurately mimic the issues that you may face due to cross-domain challenges with iframe.</td><td>💔 An appropriate dev workflow is required to test the in-development package changes within the host app locally. This is typically done with pre-publishing the package or by <a target="_blank" href="https://classic.yarnpkg.com/lang/en/docs/cli/link/">linking</a> local packages or using a tool like <a target="_blank" href="https://github.com/wclr/yalc">yalc</a>.</td></tr>
<tr>
<td><strong>Overall Recommendation ✨</strong></td><td>Suited for apps that are composed of integration with different teams who want to own their deployments and release cycles with low coupling.</td><td>Suited for apps that are rather isolated (subdomains) within a larger business domain. A question to ask is, how often would the user need to navigate between these apps? If the answer is ‘not often’, then this may be a suitable approach.</td><td>Not recommended due to the limitations that come with it. It may be suitable for some third-party integrations, for example, Twitter exposes a part of its feed which can be integrated within a website via iframe. This is rather more convenient than any of the other approaches.</td><td>Suited for apps where the changes need to be more controlled, with the host app upgrading the package, and may perform appropriate checks before releasing it to their end users.</td></tr>
</tbody>
</table>
</div><h2 id="heading-tradeoffs-and-challenges-with-module-federation">Tradeoffs and Challenges with Module Federation</h2>
<p>The primary tradeoff in using Module Federation is the initial setup effort, which I briefly discussed in the previous comparison table.</p>
<p>Here are some other challenges to anticipate when integrating via Module Federation:</p>
<h3 id="heading-setup-complexity"><strong>Setup Complexity</strong></h3>
<ol>
<li><p><strong>Bundler-specific challenges</strong> – Some things may require you to know your bundler's internals to make the integration work for your app. For example, with Webpack 5, if your remote not only exposes federated components but also serves a user experience, you’ll need the appropriate chunk setup to make that work. This is because Module Federation by default expects a certain chunk optimisation strategy and exposes the remoteEntry from the root of the app.</p>
</li>
<li><p><strong>Shared dependencies</strong> – You'll need to review your dependencies to make sure you share as many dependencies as possible to optimise bundle size and loading performance. You’ll also need to mark critical libraries (like React) as singletons to prevent runtime conflicts.</p>
</li>
</ol>
<h3 id="heading-runtime-challenges"><strong>Runtime Challenges</strong></h3>
<ol>
<li><p><strong>Cross domain</strong> – if your remote is on a different subdomain, for example <code>remote.website.com</code> and is being loaded from <code>host.website.com</code> You’ll need appropriate handling for CORS on your server to allow data fetching from the host’s subdomain. You’ll also need an appropriate <code>credentials</code> fetch configuration to make sure the browser sends the authentication cookies in data fetching requests to your remote endpoints.</p>
</li>
<li><p><strong>Styling conflicts</strong> – You’ll want to make sure the remote’s styles don’t override the host’s styles and that the remote components don’t inherit unintended styles from the host. There are multiple strategies here, from using styled components to a virtual DOM.</p>
</li>
</ol>
<h3 id="heading-operational-concerns"><strong>Operational Concerns</strong></h3>
<ol>
<li><p><strong>Observability and Analytics</strong> – Based on your requirements, you may want to either share an instance of your observability scripts, for example, an error monitoring service, or instantiate a completely different one within your MFE’s context. This becomes challenging, as there is no ‘index’ file being rendered, but rather components that are being exposed from the remotes.</p>
</li>
<li><p><strong>Deployment &amp; Caching</strong> – It’s recommended that MFE remote bundles be hosted on S3 buckets for high reliability as opposed to loading them from a remote server. You may require appropriate long-term caching for files other than the <code>remoteEntry.js</code> which is typically non-hashed and contains the link to other dependencies to be loaded.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Microfrontends offer a compelling solution for scaling frontend development across multiple teams, with Module Federation emerging as the most flexible modern approach.</p>
<p>While traditional methods like server-side composition remain valuable for specific use cases, Module Federation provides the runtime flexibility and performance characteristics needed for complex applications.</p>
<p>The decision ultimately depends on your team’s structure, technical requirements, and tolerance for implementation complexity. Start with simpler approaches if you're new to microfrontends, then consider Module Federation as your needs evolve.</p>
<h3 id="heading-whats-next">What’s next?</h3>
<p>This article was more about giving you a bird’s-eye view of the landscape. I’ll be writing more on Module Federation and going beyond the basics next. I’ll cover the technical challenges in more detail, along with possible solutions. Watch out for the same!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
