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