Next.js y Module Federation

Proyecto obsoleto

El soporte para Next.js está llegando a su fin. Más información

Next 12Next 13Next 14Next 15SSR (Pages Router) App Router No recomendado

Demo de referencia

Consulta aquí la lista de proyectos de ejemplo: Next.js SSR

Configurar el entorno

Antes de empezar, necesitas instalar Node.js y asegurarte de que tu versión de Node.js sea >= 16. Recomendamos usar la versión LTS de Node.js 24.

Puedes comprobar la versión de Node.js que estás usando con el siguiente comando:

node -v

Si no tienes Node.js instalado en tu entorno actual, o la versión instalada es demasiado antigua, puedes usar nvm o fnm para instalar la versión necesaria.

Este es un ejemplo de cómo instalar la versión LTS de Node.js 24 mediante nvm:

# Install the long-term support version of Node.js 24
nvm install 24 --lts

# Make the newly installed Node.js 24 as the default version
nvm alias default 24

# Switch to the newly installed Node.js 24
nvm use 24

Paso 1: configurar las aplicaciones Next

Crear un proyecto Next

Puedes usar create-next-app para crear un proyecto Next. Solo tienes que ejecutar el siguiente comando:

npm
yarn
pnpm
bun
npx create-next-app@latest

Crear la app 1

npx create-next-app@latest

"What is your project named?":
> mfe1

"Would you like to use App Router?":
> No

Crear la app 2

npx create-next-app@latest

"What is your project named?":
> mfe2

"Would you like to use App Router?":
> No

Instalar

cd mfe1
pnpm add @module-federation/nextjs-mf webpack -D
pnpm i
cd mfe2
pnpm add @module-federation/nextjs-mf webpack -D
pnpm i

Proyectos existentes

npm
yarn
pnpm
bun
npm i @module-federation/nextjs-mf webpack -D
next.config.mjs
import { NextFederationPlugin } from '@module-federation/nextjs-mf';

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack(config,options ){
    config.plugins.push(
      new NextFederationPlugin({
        name: 'mfe1',
        filename: 'static/chunks/remoteEntry.js',
        remotes: {
          mfe2: `http://localhost:3001/static/${options.isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
        },
        shared: {},
        extraOptions: {
          exposePages: true,
          enableImageLoaderFix: true,
          enableUrlLoaderFix: true,
        },
      })
    )
    return config
  }
};

export default nextConfig;

Paso 2: sobrescribir Webpack

Establecer la variable de entorno de Webpack local

"Webpack local" significa que debes tener webpack instalado como dependencia, y Next no usará su copia empaquetada de webpack, que no puede utilizarse porque no exporta todos los elementos internos de Webpack

shell
cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next dev
# or
cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next build

También se puede usar .env, pero puede no ser fiable a la hora de establecer NEXT_PRIVATE_LOCAL_WEBPACK a tiempo.

.env
NEXT_PRIVATE_LOCAL_WEBPACK=true

Asegúrate de instalar Webpack manualmente

Webpack debe estar instalado; de lo contrario, el build lanzará errores MODULE_NOT_FOUND

npm
yarn
pnpm
bun
npm i webpack -D

Paso 3: implementar SSR

Añadir un ciclo de vida de servidor

Para garantizar que Next.js cree un runtime de servidor, _document debe implementar getInitialProps o getServerSideProps

Sin un método de ciclo de vida de servidor, Next intentará aplicar SSG a las páginas que considere estáticas.

Sin un runtime de servidor, no hay ningún servidor que renderice las actualizaciones de los remotes

_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Añadir revalidación para la recarga en caliente en Node

Para gestionar las actualizaciones de los módulos remotos se usa la función revalidate. Esto se debe a que Webpack usa una caché de chunks y no volverá a obtener un chunk ya cargado, y un servidor en caliente no reconoce las actualizaciones automáticamente.

Hay dos formas principales de implementar la revalidación:

  • Bloqueo del renderizado (Render Blocking)
  • Stale While Revalidate
Bloqueo del renderizado
Stale While Revalidate

Esta implementación se recomienda para la mayoría de los casos de uso, ya que ayuda a evitar errores de hidratación al garantizar que el servidor y el cliente estén siempre sincronizados. Al bloquear y comprobar si hay actualizaciones antes de renderizar, puedes garantizar que tu aplicación esté siempre actualizada sin afectar negativamente a la experiencia de usuario.

Cómo funciona:

  • Antes de renderizar la página, el servidor comprueba si hay actualizaciones disponibles.
  • Si hay actualizaciones disponibles, aplica Hot Module Replacement (HMR) antes de responder a la solicitud del cliente.
  • Este método garantiza que todos los usuarios reciban la última versión de la aplicación sin encontrar incoherencias entre el contenido renderizado en el servidor y el renderizado en el cliente.

Ejemplo de implementación:

_document

import { revalidate } from '@module-federation/nextjs-mf/utils';
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    if (ctx?.pathname && !ctx?.pathname?.endsWith('_error')) {
      await revalidate().then((shouldUpdate) => {
        if (shouldUpdate) {
          console.log('Hot Module Replacement (HMR) activated', shouldUpdate);
        }
      });
    }

    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Chunk flushing

El chunk flushing intenta "volcar" los chunks usados durante el SSR para que las etiquetas <script> puedan enviarse al navegador.

_document
import Document, { Html, Head, Main, NextScript } from 'next/document';
import {
  revalidate,
  FlushedChunks,
  flushChunks,
} from '@module-federation/nextjs-mf/utils';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    if (ctx.pathname) {
      if (!ctx.pathname.endsWith('_error')) {
        await revalidate().then((shouldUpdate) => {
          if (shouldUpdate) {
            console.log('should HMR', shouldUpdate);
          }
        });
      }
    }

    const initialProps = await Document.getInitialProps(ctx);

    const chunks = await flushChunks();

    return {
      ...initialProps,
      chunks,
    };
  }

  render() {
    return (
      <Html>
        <Head>
          <FlushedChunks chunks={this.props.chunks} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Paso 4: crear y exponer

Ahora, crea un componente para exponerlo desde MFE2

Crear el componente Button

En MFE2, crea un nuevo archivo llamado Button.js en el directorio src con el siguiente contenido:

import React from 'react';

const Button = () => (
  <button>MFE2 Button</button>
);

export default Button;

Configurar Next en MFE2

Actualiza la configuración de build para exponer un módulo

next.config.mjs
const nextConfig = {
  reactStrictMode: true,
  webpack(config, options) {
    config.plugins.push(
      new NextFederationPlugin({
        name: 'mfe2',
        filename: 'static/chunks/remoteEntry.js',
        exposes: {
          "./Button": './component/Button.js',
        },
        shared: {},
        extraOptions: {
          exposePages: true,
          enableImageLoaderFix: true,
          enableUrlLoaderFix: true,
        },
      })
    )
    return config
  }
};

Paso 5: consumir el módulo remoto

Consume en MFE1 el módulo expuesto desde MFE2

Importar el módulo

Importa MFE2 en una de las páginas de MFE1

index.js
import React from 'react';
import Button from 'mfe2/Button'; // federated import

const Index = () => {
  return (
    <div>
      <h1>MFE1</h1>
      <Button />
    </div>
  );
}

Añadir un método de ciclo de vida de servidor a la página

Next también intentará aplicar SSG a las páginas que no tengan algún ciclo de vida de datos.

Asegúrate de añadir uno a los archivos de página.

export const getServerSideProps = async () => {
  return {
    props: {}
  }
}
// or
Index.getInitialProps = async ()=> {
  return {}
}

export default Index;

Configurar Next en MFE1

Actualiza el campo remotes en consecuencia

next.config.mjs
import { NextFederationPlugin } from '@module-federation/nextjs-mf';

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack(config,options ){
    config.plugins.push(
      new NextFederationPlugin({
        name: 'mfe1',
        filename: 'static/chunks/remoteEntry.js',
        remotes: {
          mfe2: `http://localhost:3001/static/${options.isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
        },
        shared: {},
        extraOptions: {
          exposePages: true,
          enableImageLoaderFix: true,
          enableUrlLoaderFix: true,
        },
      })
    )
    return config
  }
};

export default nextConfig;