SDK
SDK

Reputation: 1518

How to render NotFound page if the entered path is not matching the route in module federation react micro frontends?

Usually, we will show the NotFound page to the user if the provided path is not matching using the below code

<Route path={"*"} component={NotFound} />

But when I add this, it always navigates me to the home page which is /.

Not rendering the NotFound component.

Please help me to solve this one. The code is in this repository https://github.com/dhanushkumarsivaji/microfront-end

Webpack Code Files: -

container: (HOST)

app.js

import React, { lazy, Suspense } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import {
  StylesProvider,
  createGenerateClassName,
} from '@material-ui/core/styles';
import { createBrowserHistory } from 'history';

import Progress from './components/Progress';
import Header from './components/Header';

const MarketingLazy = lazy(() => import('./components/MarketingApp'));
const AuthAppLazy = lazy(() => import('./components/AuthApp'));

const generateClassName = createGenerateClassName({
  productionPrefix: 'co',
});

const history = createBrowserHistory();

function NotFound() {
  return (
    <h1>
        Not Found Page
    </h1>
  )
}

export default () => {
  return (
    <Router history={history}>
      <StylesProvider generateClassName={generateClassName}>
        <div>
          <Header/>
          <Suspense fallback={<Progress />}>
            <Switch>
              <Route path='/auth' component={AuthAppLazy} />
              <Route path="/" component={MarketingLazy} />
              <Route path={"*"} component={NotFound} />
            </Switch>
          </Suspense>
        </div>
      </StylesProvider>
    </Router>
  );
};

webpack.common.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react', '@babel/preset-env'],
            plugins: ['@babel/plugin-transform-runtime'],
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

webpack.dev.js

const { merge } = require('webpack-merge');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');

const devConfig = {
  mode: 'development',
  output: {
    publicPath: 'http://localhost:8080/',
  },
  devServer: {
    port: 8080,
    historyApiFallback: {
      index: 'index.html',
    },
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        marketing: 'marketing@http://localhost:8081/remoteEntry.js',
        auth: 'auth@http://localhost:8082/remoteEntry.js',
      },
      shared: packageJson.dependencies,
    }),
  ],
};

module.exports = merge(commonConfig, devConfig);

marketing: (SUB APPS)

app.js

import React from 'react';
import { Switch, Route, Router } from 'react-router-dom';
import {
  StylesProvider,
  createGenerateClassName,
} from '@material-ui/core/styles';

import Landing from './components/Landing';
import Pricing from './components/Pricing';

const generateClassName = createGenerateClassName({
  productionPrefix: 'ma',
});

export default ({ history }) => {
  return (
    <div>
      <StylesProvider generateClassName={generateClassName}>
        <Router history={history}>
          <Switch>
            <Route path="/pricing" component={Pricing} />
            <Route path="/" component={Landing} />
          </Switch>
        </Router>
      </StylesProvider>
    </div>
  );
};

webpack.common.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react', '@babel/preset-env'],
            plugins: ['@babel/plugin-transform-runtime'],
          },
        },
      },
    ],
  },
};

webpack.dev.js

const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');

const devConfig = {
  mode: 'development',
  output: {
    publicPath: 'http://localhost:8081/',
  },
  devServer: {
    port: 8081,
    historyApiFallback: true
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'marketing',
      filename: 'remoteEntry.js',
      exposes: {
        './MarketingApp': './src/bootstrap',
      },
      shared: packageJson.dependencies,
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

module.exports = merge(commonConfig, devConfig);

And below is the package version I have used:

package.json

{
  "name": "marketing",
  "version": "1.0.0",
  "scripts": {
    "start": "webpack serve --config config/webpack.dev.js",
    "build": "webpack --config config/webpack.prod.js"
  },
  "dependencies": {
    "@material-ui/core": "^4.11.0",
    "@material-ui/icons": "^4.9.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^5.3.3"
  },
  "devDependencies": {
    "@babel/core": "^7.15.8",
    "@babel/plugin-transform-runtime": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "@babel/preset-react": "^7.15.8",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^4.0.0",
    "css-loader": "^6.3.0",
    "html-webpack-plugin": "^5.3.2",
    "style-loader": "^3.3.0",
    "webpack": "^5.57.1",
    "webpack-cli": "^4.9.0",
    "webpack-dev-server": "^4.3.1",
    "webpack-merge": "^5.2.0"
  }
}

Routing Problems

And another problem in when I add the / route at the top like this

<Switch>
  <Route path="/" component={MarketingLazy} />
  <Route path='/auth' component={AuthAppLazy} />
  <Route path={"*"} component={NotFound} />
</Switch>

the components which are in the /auth route is not rendering, in this case only the / route component is rendering but the path is changing in the browser.

But when I put the / at the bottom like this, everything works fine

<Switch>
  <Route path='/auth' component={AuthAppLazy} />
  <Route path="/" component={MarketingLazy} />
  <Route path={"*"} component={NotFound} />
</Switch>

How can I resolve these issues.

Upvotes: 3

Views: 358

Answers (1)

Drew Reese
Drew Reese

Reputation: 202874

The issue is that the path="*" route is effectively unreachable because the "/" above it also matches any route.

<Switch>
  <Route path="/auth" component={AuthAppLazy} />
  <Route path="/" component={MarketingLazy} /> // <-- matches "/*"
  <Route path="*" component={NotFound} />      // <-- matches "/*" and unreachable
</Switch>

When you moved <Route path="/" component={MarketingLazy} /> to the top of the routes it then also made "/auth" path unreachable.

The Switch renders the first child <Route> or <Redirect> that matches the location. This means that path order and specificity matters. If the app renders any page on "/" and also wants to define an even less specific "catch-all" route then it needs to exactly match "/" then allow for the general fallback.

<Switch>
  <Route path="/auth" component={AuthAppLazy} />
  <Route path="/" exact component={MarketingLazy} /> // <-- matches "/" exactly
  <Route path="*" component={NotFound} />            // <-- matches "/*" and reachable
</Switch>

Upvotes: 1

Related Questions