Design and Implementation of a Micro‑Frontend Architecture for Internal Systems
This article presents a comprehensive technical study on adopting micro‑frontend architecture to solve version‑dependency, resource‑size, and navigation‑performance issues in a large internal system suite, detailing background analysis, solution research, layered architecture design, key technical challenges, custom webpack plugins, build workflow, and remaining open problems.
Micro‑frontends have emerged as a new concept in the front‑end field, aiming to split a large front‑end application into smaller, independently developable, testable, and deployable pieces while presenting a cohesive product to users. Inspired by micro‑services, this approach enables low‑coupling code composition and unified management via a base‑plus‑protocol model.
1. Background
Our company maintains many internal systems built on the same technology stack, which have revealed several problems: (1) numerous reusable components but difficulty unifying dependency versions; (2) large static asset sizes affecting load and render speed; (3) page navigation implemented via link redirects causing white‑screen and latency, harming user experience. To address these, we decided to adopt a micro‑frontend architecture for unified management.
2. Solution Research
We evaluated several industry solutions:
Route forwarding – simple but refreshes the whole page on switch.
Nested iframes – provides sandbox isolation but repeats script and style loading.
Build‑time composition – independent repositories and builds, easy dependency management, but cannot be deployed independently and requires uniform tech stacks.
Runtime composition – independent builds, managed by a host app, offering good experience and true independent deployment, yet requires complex loading, communication, and conflict‑resolution mechanisms.
Open‑source frameworks such as Alibaba's qiankun, icestark, and mooa were considered, but custom requirements (no impact on existing projects, no‑refresh loading, custom UI) led us to develop our own solution.
3. Architecture Design
3.1 Application Layer
All internal systems that join the micro‑service workbench belong to this layer. They are developed and deployed independently; only a static asset bundle specific to the micro‑service layer needs to be output.
3.2 Micro‑Service Layer
This core layer provides resource loading, routing management, state management, and user authentication. The overall workflow is illustrated below:
3.3 Base Support Layer
Acts as the foundation (base) providing the runtime environment and container for micro‑services, while also integrating other back‑end services to enrich business scenarios.
4. Technical Challenges
Implementing a custom micro‑frontend architecture requires solving several problems:
4.1 Resource Management
4.1.1 Resource Loading
Each application registers a file app.register.js containing routing and configuration information. When an app is first accessed, this file is loaded, routes are registered, and the corresponding static files are fetched based on the browser URL. The registration entry is generated via a webpack plugin, as shown below:
FuluAppRegisterPlugin.prototype.apply = function (compiler) {
appId = extraAppId();
var entry = compiler.options.entry;
if (Array.isArray(entry)) {
for (var i = 0; i < entry.length; i++) {
if (isIndexFile(entry[i])) {
indexFileEdit(entry[i]);
entry[i] = entry[i].replace(indexEntryRegx, indeEntryTemp);
i = entry.length;
}
}
} else {
if (isIndexFile(entry)) {
indexFileEdit(entry);
compiler.options.entry = compiler.options.entry.replace(indexEntryRegx, indeEntryTemp);
}
}
compiler.hooks.done.tap('fulu-app-register-done', function (compilation) {
fs.unlinkSync(tempFilePath);
return compilation;
});
compiler.hooks.emit.tap('fulu-app-register', function (compilation) {
var contentStr = 'window.register("' + appId + '", {
router: [
' + extraRouters() + '
],
entry: {
';
var entryCssArr = [];
var entryJsArr = [];
for (var filename in compilation.assets) {
if (filename.match(mainCssRegx)) {
entryCssArr.push('"' + filename + '"');
} else if (filename.match(mainJsRegx) || filename.match(manifestJsRegx) || filename.match(vendorsJsRegx)) {
entryJsArr.push('"' + filename + '"');
}
}
contentStr += 'css: [' + entryCssArr.join(', ') + '],
';
contentStr += 'js: [' + entryJsArr.join(', ') + '],
}
});
';
compilation.assets['resources/js/' + appId + '-app-register.js'] = {
source: function () { return contentStr; },
size: function () { return contentStr.length; }
};
return compilation;
});
};4.1.2 Resource File Naming
Static assets are packaged with a project‑id prefix, e.g., 10000092-main.js. The naming is adjusted via a webpack plugin.
4.2 Routing Management
Routes are divided into application‑level (prefixed with app ID) and menu‑level (managed by each app). This prevents naming collisions.
4.3 State Isolation
To keep each app’s state independent, we modify the state‑management library’s mapping rules using a webpack plugin. The plugin parses the AST, prefixes state keys with the app ID, and rewrites the code accordingly. Core implementation:
module.exports = function(source) {
var options = loaderUtils.getOptions(this);
var stuff = 'app' + options.appId;
var isView = !!~source.indexOf('React.createElement');
var allFunc = [];
var connectFn = "function connect(state){return Object.keys(state).reduce(function(obj,k){var nk=k.startsWith('"+stuff+"')?k.replace('"+stuff+"',''):k;obj[nk]=state[k];return obj;},{});}";
var connctFnAst = parser.parse(connectFn);
const ast = parser.parse(source, { sourceType: 'module', plugins: ['dynamicImport'] });
traverse(ast, {
CallExpression(path){
if (path.node.callee && path.node.callee.name === 'connect') {
// handle connect calls
}
},
FunctionDeclaration(path){
if (path.node.id.name === 'mapStateToProps' && path.node.body.type === 'BlockStatement') {
// modify mapStateToProps
}
allFunc.push(path.node);
},
ObjectExpression(path){
if (isView) return;
// modify namespace property
}
});
return core.transformFromAstSync(ast).code;
};4.4 Framework Container Rendering
After completing the above transformations, the container can render pages from each micro‑frontend, involving adjustments at the component library level.
5. Build Process
5.1 Custom Plugins
Two self‑developed plugins are used: fulu-app-register-plugin and fulu-app-loader.
5.1.1 Installation
npm i fulu-app-register-plugin fulu-app-loader -D;5.1.2 Configuration
const FuluAppRegisterPlugin = require('fulu-app-register-plugin');
module: {
rules: [{
test: /\.jsx?$/,
loader: 'fulu-app-loader'
}]
},
plugins: [
new FuluAppRegisterPlugin(),
// other plugins
];5.2 Compilation
The compilation flow remains consistent with the existing project, with an additional output for the micro‑frontend bundle.
6. Remaining Issues
6.1 JavaScript Environment Isolation
Since all apps run in the same environment, changes to shared code can affect other systems unpredictably. No robust solution is currently available; ongoing research aims to mitigate this risk.
6.2 Token Acquisition
Current app switching uses redirects to obtain tokens. To achieve true micro‑frontend behavior, this approach must be replaced with asynchronous API calls or alternative mechanisms.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Fulu Network R&D Team
Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.
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.
