How GrowingIO Leveraged Micro‑Frontends for Scalable On‑Premise Deployments
GrowingIO’s frontend team transformed its monolithic SaaS product into a micro‑frontend architecture to support on‑premise deployments, detailing the motivations, design principles, code adaptations, integration strategies, and challenges such as dependency conflicts, global variables, and style isolation.
Introduction
GrowingIO Intelligent Operations product is a one‑stop, fine‑grained operation management and data analysis platform. To support on‑premise (OP) deployments, the team needed to migrate on‑site touchpoint features from the SaaS platform to the growth platform, prompting a micro‑frontend effort.
1. What is a micro‑frontend
Micro‑frontends derive from the micro‑service concept, allowing large SPA applications to be split into smaller, independently developed and deployed front‑end apps. Benefits include application autonomy, single responsibility, and technology‑stack independence.
Application autonomy : apps follow a common interface and have no direct dependencies.
Single responsibility : each app focuses on its own features.
Technology‑stack agnostic : different apps can use Angular, React, Vue, etc.
2. Why split into micro‑frontends
The SaaS front‑end had become a monolithic “stone” app. Maintaining two separate codebases for SaaS and OP would duplicate effort and cause divergence. A micro‑frontend approach enables a single codebase to serve both environments, reducing maintenance and improving efficiency.
3. GrowingIO’s micro‑frontend implementation
3.1 Split
The Marketing app was decoupled from the SaaS app, with compatibility layers added in
@gio-bootstrapand
resourceServiceto hide environment differences.
<code>type GioBootstrap = (platform: 'saas'|'op') => Promise<ResourceMap>;</code>Resource requests are generated via
IRequestGeneratorinterfaces, supporting manual GraphQL or REST calls for OP.
<code>/**
* Request a single resource after checking dependencies.
*/
interface IRequestGenerator {
name: string;
endpoints: (projectId: string, dependencies?: {[key: string]: any}) => string[];
manual?: (projectId: string, dependencies?: {[key: string]: any}) => any;
useManual?: boolean;
dependencies?: TResource[];
persistence?: (resource: any) => void;
afterRequest?: (resource: any) => void;
noCache?: boolean;
}</code>3.2 Integration
The base layer acts as a scheduler, handling navigation and resource loading. Marketing is integrated as an independent sub‑app in both SaaS and OP platforms.
Because SaaS and OP have different dependency versions (e.g., React 16.8.3 vs 16.8.6, antd 2.x vs 3.x), runtime HTML entry integration was chosen over build‑time bundling to avoid version conflicts and preserve technology‑stack independence.
3.3 Global variable handling
To avoid naming collisions, a namespace strategy was adopted, gradually moving legacy globals (e.g.,
window.fetch, custom methods) into scoped modules.
3.4 Style isolation
Since SaaS uses an older antd version and OP a newer one, class name conflicts arise. Adjusting
style-loaderto inject styles into a dedicated DOM node per sub‑app, and using scoped APIs of
styled‑components, isolates styles during app switches.
4. Conclusion
Splitting a monolithic front‑end into micro‑frontends is iterative and requires careful handling of dependencies, globals, and styles, but adhering to technology‑stack independence enables independent packaging and deployment without affecting the host application.
GrowingIO Tech Team
The official technical account of GrowingIO, showcasing our tech innovations, experience summaries, and cutting‑edge black‑tech.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.