3 min read

How we generated multiple products UIs from a single codebase

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.

Let's Keep in Touch

Subscribe and I'll send you updates on what I'm shipping, ideas I'm exploring, and probably too many side projects.

Rougher thoughts?

My unpolished notes on building products and learning new technologies.

Explore Notes
Written by Human