How we generated multiple products UIs from a single codebase

At MyPartner ISC, we build ERPs — but not just one.

We build ERPs for schools, retail stores, logistics…
Same base idea, but every client wants it a bit different.

For a long time, we did what everyone does at first:

Copy. Paste. Pray.

It worked — until it didn’t.

  • Fixing bugs? In 3 places.
  • Adding features? Manually duplicated.
  • Refactoring? Forget it.

So we stopped duplicating and built a setup where we could power multiple product UIs from a single codebase, with:

  • microfrontends (but not the heavy kind),
  • React Router for dynamic route loading,
  • and a product config system that just works.

The big idea

We designed a simple setup:

  • One monorepo for all shared ERP modules (dashboard, HR, finance, etc.)
  • One host app per product (each ERP context)
  • A central config that tells which product shows what

From copy-paste to config-driven

Here’s how we stopped duplicating UIs:

export const PRODUCTS_CONFIG = {
  retailERP: {
    name: "Retail ERP",
    enabledModules: ["dashboard", "inventory", "sales"],
  },
  schoolERP: {
    name: "School ERP",
    enabledModules: ["dashboard", "students", "grades"],
  },
};

Each host app like retailERP-host uses its key (retailERP) to know which modules to render.

⚙️ Modules export routes

Each module exports its own routes.

// modules/students/routes.ts  export  const studentsRoutes = [
  { path: "/students", element: <StudentList /> },
];` 

In the host:

const productKey = "schoolERP"; const enabledModules = PRODUCTS_CONFIG[productKey].enabledModules; const  MODULE_ROUTES = { dashboard: dashboardRoutes, inventory: inventoryRoutes, students: studentsRoutes, grades: gradesRoutes,
}; const routes = enabledModules.flatMap( (mod) => MODULE_ROUTES[mod] || []
);` 

dynamic routes with no dead code.

🧪 Our flavor of microfrontends

We didn’t go full Module Federation. Instead:

  • Each module is isolated: UI, logic, routes, translations, etc.

  • Shared design system and internal framework

  • Hosts compose the UI by pulling modules + config

  • Everything lives in one single repo for modules

  • Each host has its own light repo (just to inject branding & build config)

So yeah — one repo to rule all ERP modules Hosts are tiny, controlled, and always in sync.

Why it was worth it

Before:

  • 3 different ERPs = 3 different frontends

  • Same “Add Product” feature implemented 3x

Now:

  • One module → enabled in any product

  • Want to launch a new ERP? Just add a config + a new host repo

  • Cleaner rollouts, less maintenance

Optional toggles

Each module can check the product config at runtime:

`const { currentProduct } = useProductConfig(); if (currentProduct.name === "School ERP") { return  <SchoolSpecificComponent />;
}` 

Final thoughts

Not everyone needs complex microfrontend infra.
Sometimes, a single repo and smart configs are all you need.

And we’ll never go back to copy-paste again.

Published: May 7, 2025