NextJS Module federation - getServerSideProps is not executed in [...slug].js file

707 views Asked by At
// ./shared.js
const React = require('react');
const createMatcher = require('feather-route-matcher');
const { injectScript } = require('@module-federation/nextjs-mf/utils');
import dynamic from 'next/dynamic';

const remoteVars = process.env.REMOTES || {};
export const remotes = Object.entries(remoteVars).reduce((acc, item) => {
  const [key, value] = item;
  if (typeof value !== 'string') {
    acc[key] = {
      global: value
    };
    return acc;
  }
  const [global, url] = value.split('@');

  acc[key] = {
    url,
    global
  };
  return acc;
}, {});

async function matchFederatedPage(path) {
  // console.log(remotes, 'shared');
  console.log(remoteVars, 'remoteVars');
  const maps = await Promise.all(
    Object.keys(remotes).map(async remote => {
      const foundContainer = injectScript(remote);
      const container = await foundContainer;

      return container
        .get('./pages-map')
        .then(factory => ({ remote, config: factory().default }))
        .catch(() => null);
    })
  );

  const config = {};

  for (const map of maps) {
    if (!map) continue;

    for (let [path, mod] of Object.entries(map.config)) {
      config[path] = {
        remote: map.remote,
        module: mod
      };
    }
  }

  console.log(config);
  const matcher = createMatcher.default(config);
  return matcher(path);
}

module.exports = {
  matchFederatedPage,
  createFederatedCatchAll() {
    const FederatedCatchAll = initialProps => {
      const [lazyProps, setProps] = React.useState({});

      const {
        FederatedPage,
        render404,
        renderError,
        needsReload,
        role,
        ...props
      } = {
        ...lazyProps,
        ...initialProps
      };
      console.log(props);


      React.useEffect(() => {
        if (needsReload) {
          const runUnderlayingGIP = async () => {
            const federatedProps = await FederatedCatchAll.getInitialProps(
              props
            );
            setProps(federatedProps);
          };
          runUnderlayingGIP();
        }
      }, []);

      if (render404) {
        // TODO: Render 404 page
        return React.createElement('h1', {}, '404 Not Found');
      }
      if (renderError) {
        // TODO: Render error page
        return React.createElement('h1', {}, 'Oops, something went wrong.');
      }

      if (FederatedPage) {
        return React.createElement(FederatedPage, props);
      }

      return null;
    };

    FederatedCatchAll.getInitialProps = async ctx => {
      // Bot marks "req, res, AppTree" as unused but those are vital to not get circular-dependency error
      const { err, req, res, AppTree, ...props } = ctx;
      if (err) {
        // TODO: Run getInitialProps for error page
        return { renderError: true, ...props };
      }

      // Custom
      let role;

      //
      if (!process.browser) {
        role = getCookie('token', req?.headers?.cookie);

        return { needsReload: true, ...props, role, remotes };
      } else {
        role = cookieCutter.get('token');
      }

      console.log('in browser', ctx.asPath);
      const matchedPage = await matchFederatedPage(ctx.asPath);

      try {
        console.log('matchedPage', matchedPage);
        const remote = matchedPage?.value?.remote;
        const mod = matchedPage?.value?.module;

        if (!remote || !mod) {
          // TODO: Run getInitialProps for 404 page
          return { render404: true, ...props };
        }

        console.log('loading exposed module', mod, 'from remote', remote);
        const container = await injectScript(remote);
        const FederatedPage = await container
          .get(mod)
          .then(factory => factory().default);
        console.log('FederatedPage', FederatedPage);
        if (!FederatedPage) {
          // TODO: Run getInitialProps for 404 page
          return { render404: true, ...props };
        }

        const modifiedContext = {
          ...ctx,
          query: matchedPage.params
        };
        const federatedPageProps =
          (await FederatedPage.getInitialProps?.(modifiedContext)) || {};
        return { ...federatedPageProps, FederatedPage, role, matchedPage };
      } catch (err) {
        console.log('err', err);
        // TODO: Run getInitialProps for error page
        return { renderError: true, ...props };
      }
    };

    return FederatedCatchAll;
  }
};

// pages/[...slug].js

import { createFederatedCatchAll } from '../shared.js';

export default createFederatedCatchAll();

Hi,

I have NextJs v13 remote and host applications. Remote exposes all its pages and it takes over when mounted in host. So all the routing is taken care from remote application.

My problem is how can I get the getServerSideProps of all the pages in remotes when each is mounted in host to be executed and pass the props to mounted remote component?

Above is the host's catchall slug route and shared.js file that does the work of finding the right module to load based on the route. Its just that getServerSideProps dont get executed and its a bottleneck in my POC.

I tried to run matchFederatedPage('/path') from nextjs page/getServersideProps function but it throws errors saying unhandledRejection: TypeError: Cannot read property 'asyncContainer' of undefined

Is there any way to load up the right module + have the getServerSideProps function executed of remotes in slug route of host?

Here are my repo links

Host - https://github.com/gitneeraj/nextjs-mf-host

Remote1 - https://github.com/gitneeraj/nextjs-mf-remote1

PS - I have already posted this on examples repo so sorry for the duplication. I really need this to get working in my project.

~ Thanks & Peace

0

There are 0 answers