I am folowing microforntends architecture
using this module federation package "@module-federation/nextjs-mf"
.
I have 3 NextJS Apps
- One
main-app
which is the host application, - Two microfrontends
first-app
andsecond-app
here is thenext.config.js
for each app :
main-app : (HOST APPLICATION)
const path = require("path");
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");
const { createDelegatedModule } = require('@module-federation/nextjs-mf/utilities');
const nextConfig = async () => {
return {
reactStrictMode: true,
transpilePackages: ["ui", "core"],
output: "standalone",
experimental: {
outputFileTracingRoot: path.join(__dirname, "../../"),
},
swcMinify: true,
webpack: (config, options) => {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: "application",
remotes: {
firstapplication:"firstapplication@http://localhost:4001/_next/static/chunks/remoteEntry.js",
secondapplication:"secondapplication@http://localhost:4002/_next/static/chunks/remoteEntry.js",
},
exposes: {
"./Page": "./pages",
},
filename: "static/chunks/remoteEntry.js",
extraOptions: {
exposePages: true,
},
})
);
return config;
},};
};
module.exports = nextConfig;
first-app: (MICROFRONTENDS)
const path = require("path");
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["ui", "core"],
output: "standalone",
experimental: {
outputFileTracingRoot: path.join(__dirname, "../../"),
},
swcMinify: true,
webpack: (config, options) => {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: "firstapplication",
remotes: {
application: `application@http://localhost:4000/_next/static/${
isServer ? "ssr" : "chunks"
}/remoteEntry.js`,
},
exposes: {
"./Page": "./pages",
},
filename: "static/chunks/remoteEntry.js",
extraOptions: {
exposePages: true,
},
})
);
return config;
},
};
module.exports = nextConfig;
second-app: (MICROFRONTENDS)
const path = require("path");
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["ui", "core"],
output: "standalone",
experimental: {
outputFileTracingRoot: path.join(__dirname, "../../"),
},
swcMinify: true,
webpack: (config, options) => {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: "secondapplication",
remotes: {
application: `application@http://localhost:4000/_next/static/${
isServer ? "ssr" : "chunks"
}/remoteEntry.js`,
},
exposes: {
"./Page": "./pages",
},
filename: "static/chunks/remoteEntry.js",
extraOptions: {
exposePages: true,
},
})
);
return config;
},
};
module.exports = nextConfig;
The main-app is showing those packages normaly this is the index of the main-app :
const FirstApp = dynamic(() => import("firstapplication/Page"), { ssr: false });
const SecondApp = dynamic(() => import("secondapplication/Page"), {
ssr: false,
});
// const App = lazy(() => window.next2.get('./firstapplication/Page').then((factory) => {
// return {default: factory()}
// }));
//const App = lazy(() => import("firstapplication/Page"));
export default function Home() {
const getUserByIdUseCase = useInjection(TYPES.IGetUserByIdUseCase);
const getLoginUseCase = useInjection(TYPES.ILoginUseCase);
const {i18n , t} = useTranslation();
// const { featuresflags } = useFeaturesSync();
const dispatch = useDispatch();
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const errorselector = useSelector((state) => state.error.error);
//console.log("this is it +++ :" , flags) ;
//console.log("this is it +++ :" , featuresflags) ;
const auth = useSelector((state) => state.auth.auth);
const [shouldRerender, setShouldRerender] = useState(true);
// useEffect(() => {
// console.log("this is it useEffect :" , flags) ;
// console.log("this is it useEffect :" , featuresflags) ;
// if (JSON.stringify(flags) !== JSON.stringify(featuresflags)) {
// dispatch(featuresActions.setFeatures(flags));
// }
// }, [flags, featuresflags, dispatch]);
useEffect(()=>{
// const fetchData = async () => {
// const userId = 'someUserId';
// const response = await getUserByIdUseCase.execute(userId);
// alert('this is youre response'+JSON.stringify(response));
// console.log(response);
// };
// fetchData();
},[]);
const loginHandler = () => {
const fetchData = async () => {
const request = {
username: "admin",
password: "Mazars@@**",
};
const response = await getLoginUseCase.execute(request);
const error = new CustomError("This is a custom error message");
dispatch(errorActions.setError(error));
//dispatch(featuresActions.setFeatures({amine:"amine"}));
const AnotherError = new Error("This is an error message");
dispatch(errorActions.setError(AnotherError));
};
fetchData();
};
const logoutHandler = () => {
dispatch(authActions.logout());
};
const handleSelectionChange = (selectedValue) => {
if (selectedValue === "first") {
setShouldRerender(true);
i18n.changeLanguage('fr');
localStorage.setItem("language" , "fr");
} else {
setShouldRerender(false);
i18n.changeLanguage('en');
localStorage.setItem("language" , "en");
}
// You can perform any actions with the selected value here
};
return (
<Box height="100vh" display="flex" flexDirection="column">
<Box>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
{isAuthenticated && (
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
{t('greeting')} { auth.username}
</Typography>
)}
{errorselector !== null && <Typography variant="h6" component="div" sx={{ flexGrow: 1 }} > "here i am " {JSON.stringify(errorselector)}</Typography>}
{!isAuthenticated && (
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
{t('greeting')} The Host Application
</Typography>
)}
<SelectorComponent onSelectionChange={handleSelectionChange} />
{isAuthenticated && (
<Button color="inherit" onClick={logoutHandler}>
Logout
</Button>
)}
{!isAuthenticated && (
<Button color="inherit" onClick={loginHandler}>
Login
</Button>
)}
</Toolbar>
</AppBar>
</Box>
<Box flex={1} overflow="auto">
{shouldRerender && (
<FirstApp style={{ flex: 1, height: "100%" }}></FirstApp>
)}
{!shouldRerender && (
<SecondApp style={{ flex: 1, height: "100%" }}></SecondApp>
)}
</Box>
<Footer />
</Box>
);
}
The problem happens with 2 packages
till now , this time with translation package i setup the translation package in the first-app
and it works only if i use the microservice url
when i access the microfrontend directly, but not when it’s integreated into the main-app (using the host application url ) , in first-app
it render the correct translation but in the host application it render greeting and not the hello coming from the translation hook ('greeting')
- Note : i am using this package for translation :
https://react.i18next.com/