NextFederationPlugin vs ModuleFederationPlugin

Published: April 18, 2026 · 14 min read

NextFederationPlugin vs ModuleFederationPlugin: Complete Guide

You built a React micro frontend architecture with ModuleFederationPlugin — a host app loading remote MFEs via remoteEntry.js, shared singleton dependencies, and lazy loading with Suspense. Now you need to add a Next.js application to the same system. You drop ModuleFederationPlugin into next.config.js, run the build, and get a wall of errors. NextFederationPlugin vs ModuleFederationPlugin — they solve the same problem (sharing modules between applications at runtime) but they configure, serve, and load those modules in fundamentally different ways.

This is Article 17 in the Micro Frontend Architecture series and the first article in the Next.js MFE section. If you haven't completed the React MFE section, start with the Module Federation Complete Guide.

NextFederationPlugin vs ModuleFederationPlugin comparison diagram showing the architectural differences between React and Next.js module federation

The Problem — ModuleFederationPlugin Doesn't Work with Next.js

ModuleFederationPlugin (opens in a new tab) is a built-in webpack 5 plugin. It works perfectly with React SPAs where you control the entire webpack configuration. But Next.js abstracts webpack behind next.config.js — you cannot drop a raw webpack plugin into a Next.js config and expect it to work.

The issues:

  1. Next.js runs webpack twice — once for the server bundle (Node.js) and once for the client bundle (browser). ModuleFederationPlugin generates one remoteEntry.js — Next.js needs two (one for SSR, one for the client).
  2. Asset paths differ — Next.js serves static assets from /_next/static/ not from the root. A remoteEntry.js at /remoteEntry.js returns 404 because Next.js does not serve files from the root static directory.
  3. next/image breaks — The image loader resolves URLs against the host's domain, not the remote's domain. Images in federated components return 404.
  4. No automatic async boundary — Module Federation requires an async entry point for shared dependency negotiation. React MFEs use a manual bootstrap.js file. Next.js has no equivalent mechanism without plugin support.

NextFederationPlugin (opens in a new tab) from @module-federation/nextjs-mf solves all four issues by wrapping ModuleFederationPlugin with Next.js-specific handling.

What Is NextFederationPlugin?

NextFederationPlugin is a wrapper around ModuleFederationPlugin that adapts Module Federation for Next.js's dual-build architecture (server + client), asset serving conventions, and SSR requirements. It is maintained by the Module Federation team (opens in a new tab) and distributed as @module-federation/nextjs-mf.

NextFederationPlugin uses ModuleFederationPlugin internally. It is not a replacement — it is an adapter. Every feature of Module Federation (shared dependencies, remote containers, exposed modules) works the same way. NextFederationPlugin adds the Next.js-specific configuration that ModuleFederationPlugin does not know about.

Install it in your Next.js application:

npm install @module-federation/nextjs-mf

Side-by-Side Comparison

FeatureModuleFederationPlugin (React)NextFederationPlugin (Next.js)
PackageBuilt into webpack 5@module-federation/nextjs-mf
Config filewebpack.config.jsnext.config.js
Import statementrequire("webpack").containerrequire("@module-federation/nextjs-mf")
remoteEntry.js path/remoteEntry.js (root)/_next/static/chunks/remoteEntry.js
SSR supportNoYes (separate ssr/chunks entries)
isServer checkNot neededRequired for Next.js remote paths
basePath / assetPrefixNot used (publicPath only)Required for subdirectory routing
output: 'standalone'Not neededRequired for Docker deployment
extraOptionsNot availableexposePages, enableImageLoaderFix, etc.
Loading remote componentsReact.lazy() + Suspensenext/dynamic with ssr: false
Routingreact-router-domnext/router (file-based)
splitChunks (dev)Disabled manuallyManaged by Next.js
splitChunks (prod)Manual cacheGroups configManaged by Next.js
publicPath (dev)https://localhost:PORT/Automatic via dev server
publicPath (prod)/app-name/basePath + assetPrefix
Shared dep versionsrequiredVersion from package.jsonrequiredVersion: false for core libs
Image handlingStandard img tagsnext/image with remotePatterns
Security headersManual webpack configBuilt-in via headers() in next.config.js

NextFederationPlugin vs ModuleFederationPlugin feature comparison table for React and Next.js micro frontends

Configuration Comparison — React vs Next.js

The configuration files look structurally different even though they achieve the same goal: expose modules from a remote application and load them in a host application.

React Remote (ModuleFederationPlugin)

Local development and production configs are fundamentally different. Local dev uses https://localhost:PORT with CORS headers and disables splitChunks for faster rebuilds. Production uses path-based URLs (/products/) with content-hash filenames and vendor chunk splitting.

apps/Products/webpack.config.js
// apps/Products/webpack.config.js — React Remote (Local Development)
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;

module.exports = {
  mode: "development",
  entry: path.resolve(__dirname, "src", "index.js"),
  output: {
    // Full localhost URL — webpack-dev-server serves from this origin
    publicPath: "https://localhost:4002/",
    filename: "[name].bundle.js",
    clean: true,
  },
  devServer: {
    port: 4002,
    https: true,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
      "Access-Control-Allow-Headers": "X-Requested-With, Content-Type",
    },
    historyApiFallback: true,
    hot: true,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "Products",
      filename: "remoteEntry.js",
      exposes: {
        "./ProductsMFE": "./src/components/ProductsMFE.jsx",
        "./ProductList": "./src/components/ProductList.jsx",
        "./ProductDetail": "./src/components/ProductDetail.jsx",
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
        "react-router-dom": { singleton: true, requiredVersion: deps["react-router-dom"] },
        "@myapp/store": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
      },
    }),
    new HtmlWebpackPlugin({ template: "./public/index.html" }),
    new MiniCssExtractPlugin(),
  ],
  optimization: {
    // splitChunks DISABLED in local dev — faster rebuilds
    splitChunks: false,
  },
  // ... loaders omitted for brevity
};

Next.js Remote (NextFederationPlugin)

apps/Content/next.config.js
// apps/Content/next.config.js — Next.js Remote (Production)
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");

/** @type {import('next').NextConfig} */
const nextConfig = {
  // basePath and assetPrefix route all assets under /content/
  // so Nginx can proxy requests to this remote's container
  basePath: "/content",
  assetPrefix: "/content",

  // standalone output for Docker deployment
  output: "standalone",

  reactStrictMode: true,

  transpilePackages: ["@myapp/store", "@myapp/api", "@myapp/seo"],

  images: {
    remotePatterns: [
      { protocol: "https", hostname: "cdn.myapp.com" },
      { protocol: "https", hostname: "ik.imagekit.io" },
    ],
  },

  webpack(config, { isServer }) {
    config.plugins.push(
      new NextFederationPlugin({
        name: "Content",
        // Next.js convention — remoteEntry lives inside _next/static/chunks/
        filename: "static/chunks/remoteEntry.js",
        exposes: {
          "./FAQ": "./components/FAQ",
          "./TermsAndConditions": "./components/TermsAndConditions",
          "./PrivacyPolicy": "./components/PrivacyPolicy",
          "./SizeGuide": "./components/SizeGuide/SizeGuide",
        },
        shared: {
          react: { singleton: true, requiredVersion: false, eager: false },
          "react-dom": { singleton: true, requiredVersion: false, eager: false },
          "react-redux": { singleton: true, requiredVersion: false, eager: false },
          "@reduxjs/toolkit": { singleton: true, requiredVersion: false, eager: false },
          "@myapp/store": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
          "@myapp/api": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
          "@myapp/seo": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
        },
        extraOptions: {
          automaticAsyncBoundary: true,
        },
      })
    );
    return config;
  },
};

module.exports = nextConfig;

Notice the differences from the React remote:

  • basePath and assetPrefix replace webpack's publicPath — they tell Next.js to scope all routes and assets under /content/
  • output: "standalone" builds a self-contained deployment folder (essential for Docker)
  • filename: "static/chunks/remoteEntry.js" follows the Next.js asset convention instead of serving from root
  • transpilePackages replaces webpack's babel-loader include paths for shared packages
  • requiredVersion: false for core libraries — Next.js manages React versions internally

remoteEntry.js Path Differences

The most common mistake when mixing React and Next.js remotes: using the wrong remoteEntry.js path. React and Next.js serve the remote entry file from completely different locations.

remote-entry-paths.txt
Remote Entry Path — React vs Next.js:

React MFE (ModuleFederationPlugin):
  filename: "remoteEntry.js"
  Served at: /products/remoteEntry.js
  Host loads: Products@https://dev.myapp.com/products/remoteEntry.js
  Same file for client and server — no SSR distinction.

Next.js MFE (NextFederationPlugin):
  filename: "static/chunks/remoteEntry.js"
  Served at: /content/_next/static/chunks/remoteEntry.js   (client)
             /content/_next/static/ssr/remoteEntry.js       (server)
  Host loads:
    Client: Content@https://dev.myapp.com/content/_next/static/chunks/remoteEntry.js
    Server: Content@https://dev.myapp.com/content/_next/static/ssr/remoteEntry.js

Why two paths?
  Next.js renders pages on the server (SSR) before sending HTML to the browser.
  The server needs a Node.js-compatible remoteEntry (no window, no DOM).
  The client needs a browser-compatible remoteEntry (with window, DOM access).
  NextFederationPlugin generates BOTH — the host picks the right one using isServer.
⚠️

If you use a React-style path (/remoteEntry.js) for a Next.js remote, the host gets a 404. The file does not exist at the root — NextFederationPlugin places it at _next/static/chunks/remoteEntry.js. Conversely, using the Next.js path for a React remote also returns 404. Always check which plugin built the remote before writing the remote URL.

The isServer Check — SSR-Aware Remote Loading

Next.js runs webpack twice: once for the server bundle and once for the client bundle. The isServer flag is passed to the webpack function in next.config.js and determines which remoteEntry.js path to use for Next.js remotes.

next.config.js
// next.config.js — isServer determines which remoteEntry to load
webpack(config, { isServer }) {
  config.plugins.push(
    new NextFederationPlugin({
      name: "Main",
      remotes: {
        // React remote — same remoteEntry for client AND server
        Auth: "Auth@https://dev.myapp.com/auth/remoteEntry.js",

        // Next.js remote — DIFFERENT remoteEntry for client vs server
        Content: `Content@https://dev.myapp.com/content/_next/static/${
          isServer ? "ssr" : "chunks"
        }/remoteEntry.js`,

        Products: `Products@https://dev.myapp.com/products/_next/static/${
          isServer ? "ssr" : "chunks"
        }/remoteEntry.js`,
      },
      // ...
    })
  );
}

// What happens at build time:
//
// 1. Next.js runs webpack TWICE — once for server, once for client.
//
// 2. Server build (isServer = true):
//    Content → loads /content/_next/static/ssr/remoteEntry.js
//    This entry is a CommonJS module that runs in Node.js.
//    It can access the filesystem, has no window or document.
//
// 3. Client build (isServer = false):
//    Content → loads /content/_next/static/chunks/remoteEntry.js
//    This entry is a browser script that attaches to window.
//    It uses the DOM, can access localStorage, runs in the browser.
//
// 4. React remotes (Auth, Cart) use the SAME remoteEntry.js
//    for both builds because they are client-only — the host
//    loads them with ssr: false via next/dynamic.

Mixing React and Next.js Remotes in One Host

A Next.js host application can load both React remotes and Next.js remotes simultaneously. The host uses NextFederationPlugin to define all remotes, but the path format differs based on which plugin built the remote.

common-mistakes.txt
// WRONG — Using ModuleFederationPlugin path for Next.js remote
remotes: {
  Content: "Content@https://dev.myapp.com/content/remoteEntry.js",
  //                                             ^ WRONG PATH
  // This looks for remoteEntry.js at the content root — but
  // NextFederationPlugin places it at _next/static/chunks/remoteEntry.js.
  // Result: 404 Not Found. The host cannot load the Content remote.
}

// CORRECT — Using the Next.js _next/static path with isServer check
remotes: {
  Content: `Content@https://dev.myapp.com/content/_next/static/${
    isServer ? "ssr" : "chunks"
  }/remoteEntry.js`,
  //  _next/static/chunks/ for browser builds
  //  _next/static/ssr/ for server-side rendering
}

// WRONG — Using the same isServer path for React remotes
remotes: {
  Auth: `Auth@https://dev.myapp.com/auth/_next/static/${
    isServer ? "ssr" : "chunks"
  }/remoteEntry.js`,
  //                                    ^ WRONG
  // React MFEs don't have _next/static/ — they use plain webpack output.
  // Result: 404 Not Found. The host cannot load the Auth remote.
}

// CORRECT — React remotes use simple root-level remoteEntry.js
remotes: {
  Auth: "Auth@https://dev.myapp.com/auth/remoteEntry.js",
  //                                     ^ Simple path — no _next/static/
}

Host Configuration — React Host vs Next.js Host

The host application's configuration differs significantly depending on whether it is a React SPA or a Next.js application.

apps/Main/webpack.config.js
// apps/Main/webpack.config.js — React Host (Local Development)
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;

module.exports = {
  mode: "development",
  output: {
    publicPath: "/",
    filename: "[name].bundle.js",
  },
  devServer: {
    port: 4000,
    https: true,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
      "Access-Control-Allow-Headers": "X-Requested-With, Content-Type",
    },
    historyApiFallback: true,
    hot: true,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "Main",
      // Each remote points to localhost:PORT — individual dev servers
      remotes: {
        Products: "Products@https://localhost:4002/remoteEntry.js",
        Orders: "Orders@https://localhost:4003/remoteEntry.js",
        Inventory: "Inventory@https://localhost:4004/remoteEntry.js",
        Pricing: "Pricing@https://localhost:4005/remoteEntry.js",
        Analytics: "Analytics@https://localhost:4006/remoteEntry.js",
        Settings: "Settings@https://localhost:4007/remoteEntry.js",
        Support: "Support@https://localhost:4008/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
        "react-router-dom": { singleton: true, requiredVersion: deps["react-router-dom"] },
        "@reduxjs/toolkit": { singleton: true, requiredVersion: deps["@reduxjs/toolkit"] },
        "react-redux": { singleton: true, requiredVersion: deps["react-redux"] },
        "@myapp/store": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
      },
    }),
  ],
  optimization: {
    splitChunks: false,
  },
};

extraOptions — Next.js-Specific Configuration

NextFederationPlugin provides an extraOptions object that handles Next.js-specific concerns. These options do not exist in ModuleFederationPlugin because they solve problems unique to the Next.js framework.

next.config.js
// next.config.js — extraOptions (NextFederationPlugin only)
new NextFederationPlugin({
  name: "Main",
  filename: "static/chunks/remoteEntry.js",
  remotes: { /* ... */ },
  shared: { /* ... */ },
  extraOptions: {
    // Expose all Next.js pages as federated modules automatically.
    // Without this, you must manually add each page to the exposes map.
    // The host can then import any page from the remote like a component.
    exposePages: true,

    // Fix next/image component when loaded from a federated remote.
    // Without this, images in remote components fail to resolve
    // because the image loader URL points to the host's domain
    // instead of the remote's domain.
    enableImageLoaderFix: true,

    // Fix static assets (fonts, SVGs) loaded via url-loader in remotes.
    // Without this, asset URLs resolve relative to the host's publicPath
    // instead of the remote's basePath — returning 404 for every asset.
    enableUrlLoaderFix: true,

    // Wrap exposed modules in an async boundary automatically.
    // This enables top-level await for shared dependency negotiation.
    // Without this, you must manually create an async bootstrap file
    // (the same bootstrap.js pattern used in React MFEs).
    automaticAsyncBoundary: true,
  },
});

// These options do NOT exist in ModuleFederationPlugin.
// React MFEs handle each of these manually:
//   - exposePages: not applicable (React has no pages directory)
//   - enableImageLoaderFix: not applicable (no next/image)
//   - enableUrlLoaderFix: handled by publicPath in webpack output
//   - automaticAsyncBoundary: requires manual bootstrap.js file

Shared Dependencies — Same Pattern, Different Defaults

Both plugins use the same shared configuration structure, but the defaults differ because Next.js manages its own React version and routing.

shared-deps-comparison.js
// Shared dependencies — React MFE vs Next.js MFE

// React MFE (ModuleFederationPlugin) — webpack.config.js
shared: {
  react: { singleton: true, requiredVersion: deps.react },
  "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
  "react-router-dom": { singleton: true, requiredVersion: deps["react-router-dom"] },
  "@reduxjs/toolkit": { singleton: true, requiredVersion: deps["@reduxjs/toolkit"] },
  "react-redux": { singleton: true, requiredVersion: deps["react-redux"] },
  "@myapp/store": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
  "@myapp/api": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
}

// Next.js MFE (NextFederationPlugin) — next.config.js
shared: {
  react: { singleton: true, requiredVersion: false, eager: false },
  "react-dom": { singleton: true, requiredVersion: false, eager: false },
  "react-redux": { singleton: true, requiredVersion: false, eager: false },
  "@reduxjs/toolkit": { singleton: true, requiredVersion: false, eager: false },
  "@myapp/store": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
  "@myapp/api": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
  "@myapp/seo": { singleton: true, strictVersion: true, requiredVersion: "1.0.0" },
}

// Key differences:
//
// 1. requiredVersion: false (Next.js) vs deps.react (React)
//    Next.js manages React versions internally — forcing a specific
//    version can conflict with Next.js's own React dependency.
//    React MFEs read the version from package.json to enforce matching.
//
// 2. eager: false is explicit in Next.js, implicit in React.
//    Both default to false, but Next.js configs set it explicitly
//    to prevent accidental eager loading that breaks SSR hydration.
//
// 3. react-router-dom is shared in React MFEs but NOT in Next.js.
//    Next.js uses its own next/router — no React Router needed.
//
// 4. @myapp/seo is shared in Next.js but not always in React MFEs.
//    SEO utilities (meta tags, structured data) only matter for
//    server-rendered pages — React SPAs handle SEO differently.

Loading Remote Components — React.lazy vs next/dynamic

How the host application loads remote MFE components also differs between React and Next.js hosts. React uses React.lazy() with Suspense, while Next.js uses next/dynamic with ssr: false. For a deep dive into lazy loading patterns, see Lazy Loading Micro Frontends with React Suspense.

src/App.jsx
// src/App.jsx — React Host loading remote MFE with React.lazy()
import React, { Suspense, lazy } from "react";
import { Routes, Route } from "react-router-dom";
import ErrorBoundary from "./components/ErrorBoundary";

// React.lazy wraps the Module Federation import
// The remote loads ONLY when the user navigates to /products
const ProductsMFE = lazy(() =>
  import("Products/ProductsMFE").catch((err) => {
    console.error("Failed to load ProductsMFE", err);
    return { default: () => <div>Failed to load Products</div> };
  })
);

const OrdersMFE = lazy(() =>
  import("Orders/OrdersMFE").catch((err) => {
    console.error("Failed to load OrdersMFE", err);
    return { default: () => <div>Failed to load Orders</div> };
  })
);

function App() {
  return (
    <Routes>
      <Route path="products/*" element={
        <ErrorBoundary>
          <Suspense fallback={<div>Loading Products...</div>}>
            <ProductsMFE />
          </Suspense>
        </ErrorBoundary>
      } />
      <Route path="orders/*" element={
        <ErrorBoundary>
          <Suspense fallback={<div>Loading Orders...</div>}>
            <OrdersMFE />
          </Suspense>
        </ErrorBoundary>
      } />
    </Routes>
  );
}
⚠️

ssr: false is mandatory for ALL Module Federation remotes in Next.js — including Next.js remotes loaded via NextFederationPlugin. Even though NextFederationPlugin generates an SSR-compatible remoteEntry, the next/dynamic call on the host side must disable SSR for the remote component import. The SSR remoteEntry handles the server-side module resolution, but the actual component rendering must happen on the client after hydration.

NextFederationPlugin isServer dual-build architecture showing separate SSR and client remoteEntry paths for Next.js micro frontends

When to Use Which Plugin

ScenarioPluginWhy
React SPA host + React SPA remotesModuleFederationPluginNo SSR needed, full webpack control
Next.js host + React remotesNextFederationPlugin (host) + ModuleFederationPlugin (remotes)Host needs SSR-aware loading, remotes are client-only
Next.js host + Next.js remotesNextFederationPlugin (both)Both need SSR support and Next.js asset conventions
Next.js host + mixed remotesNextFederationPlugin (host) + both plugins (remotes)Host handles both path formats via isServer check
React host + Next.js remotesModuleFederationPlugin (host) — use _next/static/chunks/ pathHost loads the client-side entry directly

What's Next?

Now that you understand the differences between NextFederationPlugin and ModuleFederationPlugin, the next article walks through setting up a Next.js host application that loads both React and Next.js remote MFEs — from next.config.js configuration to routing, SSR handling, and deployment.

← Back to Lazy Loading with React Suspense

Continue to Next.js MFE Host App Setup →


Frequently Asked Questions

What is the difference between NextFederationPlugin and ModuleFederationPlugin?

ModuleFederationPlugin is built into webpack 5 and works with React SPAs — it uses webpack.config.js, serves remoteEntry.js at the root path, and has no SSR support. NextFederationPlugin is a separate package (@module-federation/nextjs-mf) designed for Next.js — it uses next.config.js, serves remoteEntry.js at _next/static/chunks/ (client) and _next/static/ssr/ (server), supports server-side rendering via the isServer flag, and provides extra options like exposePages and enableImageLoaderFix that do not exist in the standard webpack plugin.

Why does NextFederationPlugin have a different remoteEntry.js path?

NextFederationPlugin places remoteEntry.js at _next/static/chunks/remoteEntry.js instead of the root because Next.js has a specific asset serving convention. All static assets live under _next/static/ — JavaScript chunks go in chunks/, CSS goes in css/, and SSR bundles go in ssr/. NextFederationPlugin follows this convention so the remote entry integrates with Next.js's asset serving, cache invalidation, and CDN deployment. It also generates a separate SSR entry at _next/static/ssr/remoteEntry.js for server-side rendering.

Can I mix React and Next.js remotes in the same host application?

Yes. A Next.js host using NextFederationPlugin can load both React remotes (built with ModuleFederationPlugin) and Next.js remotes (built with NextFederationPlugin) simultaneously. The key is using the correct remoteEntry.js path for each type — React remotes use a simple path like /auth/remoteEntry.js, while Next.js remotes use the _next/static path with the isServer ternary. React remotes must be loaded with next/dynamic using ssr: false since they are client-only. Next.js remotes can optionally support SSR.

What are extraOptions in NextFederationPlugin?

extraOptions is a configuration object unique to NextFederationPlugin that handles Next.js-specific concerns. exposePages automatically exposes all Next.js pages as federated modules without manually listing each one. enableImageLoaderFix corrects next/image component URLs when loaded from a remote so images resolve against the remote's domain instead of the host's. enableUrlLoaderFix fixes static asset paths (fonts, SVGs) in remotes. automaticAsyncBoundary wraps exposed modules in an async boundary for shared dependency negotiation — replacing the manual bootstrap.js file needed in React MFEs.

Do I need to use next/dynamic instead of React.lazy in a Next.js host?

Yes, use next/dynamic instead of React.lazy() when your host is a Next.js application. The critical difference is SSR handling — next/dynamic accepts an ssr: false option that tells Next.js to skip the component during server-side rendering. Module Federation remotes attach to the window object, which does not exist on the server. Without ssr: false, the build crashes with a reference error. React.lazy() does not have this option and is client-only by design, making it suitable only for React SPA hosts, not Next.js hosts.

Why is basePath required for Next.js remotes but not React remotes?

Next.js remotes need basePath and assetPrefix because Next.js generates internal routes and asset URLs that must be scoped to the remote's subdirectory. Without basePath set to /content, a Next.js remote's internal navigation would resolve to / instead of /content/, breaking routing. assetPrefix ensures that CSS, images, and JavaScript chunks are fetched from /content/_next/ instead of /_next/. React MFEs handle this through webpack's publicPath setting alone — there is no internal routing framework that needs subdirectory scoping.