How to Fix Stack Trace Column and Line Offsets in Micro‑Frontend Frameworks (qiankun & micro‑app)
This article analyzes why micro‑frontend frameworks like qiankun and micro‑app cause error stack column and line offsets, demonstrates the root cause of code injection, and provides both framework‑specific and universal solutions to adjust stack traces for accurate debugging and monitoring.
As projects grow and business lines split into independent teams, micro‑frontend can effectively integrate multiple projects.
In a monolithic app, error stacks point precisely to the source code, but under micro‑frontend architectures the isolation mechanisms alter the runtime environment, causing stack column and line numbers to mismatch the original code.
qiankun and micro‑app are two mainstream micro‑frontend solutions; they use JavaScript and style sandboxes, which lead to stack offset issues that require parsing error information to locate the correct source.
Case 1: Column offset in qiankun
Related issue: https://github.com/umijs/qiankun/issues/1088#issuecomment-1021894711
Phenomenon and data comparison
A project using a Vue2 main app and a React sub‑app with qiankun 2.6.3 shows different column numbers in micro‑frontend and non‑micro‑frontend environments.
Micro‑frontend environment: column
20935 { "message":"测试错误", "stack":"Error: 测试错误
at eval (.../js/453.1fbd684b.async.js:1:20935)" }Non‑micro‑frontend environment: column
20845 { "message":"测试错误", "stack":"Error: 测试错误
at .../js/453.1fbd684b.async.js:1:20845" }The column difference is 90, stable across multiple tests.
Root cause: Code injection causing column offset
Non‑micro‑frontend code snapshot:
Micro‑frontend code snapshot:
Debugging shows qiankun injects a prefix of 90 characters before the original code, for example:
window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){;This injected code shifts column numbers by 90. It is part of qiankun's JavaScript sandbox implementation, a trade‑off between runtime isolation and debugging experience.
Solution: Adjust column when reporting errors
For error‑monitoring tools such as Sentry, manually subtract the injected code length before sending the event.
Sentry.init({
beforeSend(event) {
const QIANKUN_INJECTED_CODE = 'window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){;';
const INJECTED_CODE_LENGTH = QIANKUN_INJECTED_CODE.length;
const { stacktrace: { frames }, ...rest } = item;
const lastFrame = frames[frames.length - 1];
if (typeof lastFrame.colno === 'number') {
lastFrame.colno -= INJECTED_CODE_LENGTH;
}
// ...
},
});Limitation
This only fixes the stack information sent to monitoring platforms; it cannot correct the offset shown in the browser console because the console reads the runtime code directly.
Case 2: Line offset in micro‑app
Related issue: https://github.com/jd-opensource/micro-app/issues/935
Phenomenon and data comparison
Another project using a Vue2 main app and a React sub‑app with @micro-zoe/micro-app@^0.8.3 shows a stable line offset of 2.
Micro‑frontend environment: line
4 TypeError: Cannot read properties of undefined (reading 'name_cn')
at onClick (https://xxx.com/js/488.dbe55ec7.js:4:97551)
...Non‑micro‑frontend environment: line
2 TypeError: Cannot read properties of undefined (reading 'name_cn')
at onClick (https://xxx.com/js/488.dbe55ec7.js:2:97551)
...The stable line difference of 2 is caused by micro‑app's injected initialization code.
Root cause: Code injection causing line offset
Non‑micro‑frontend code snapshot:
Micro‑frontend code snapshot:
Debugging shows micro‑app injects a prefix similar to:
(function anonymous() {;(function(proxyWindow){with(proxyWindow.__MICRO_APP_WINDOW__){(function(window,self,globalThis,document,Document,Array,Object,String,Boolean,Math,Number,Symbol,Date,Function,Proxy,WeakMap,WeakSet,Set,Map,Reflect,Element,Node,RegExp,Error,TypeError,JSON,isNaN,parseFloat,parseInt,performance,console,decodeURI,encodeURI,decodeURIComponent,encodeURIComponent,navigator,undefined,location,history){;Solution approach (framework‑specific fix)
Similar to qiankun, adjust the line number in the error report.
Determine the number of injected lines (2 in this case).
Subtract this offset from the line value in the stacktrace before sending.
Sentry.init({
beforeSend(event) {
const MICRO_APP_LINE_OFFSET = 2;
const adjustedFrames = item.stacktrace.frames.map(frame => {
if (typeof frame.lineno === 'number') {
return { ...frame, lineno: frame.lineno - MICRO_APP_LINE_OFFSET };
}
return frame;
});
// ...
},
});Limitation
Again, this only fixes monitoring platform stacks, not the browser console offset.
From targeted fixes to a universal solution
The goal is to automatically correct line and column numbers in error stacks and make browser console clicks navigate to the original source.
Targeted fixes
Early hard‑coded fixes for each framework, e.g., subtracting qiankun's injected code length in Sentry.
Sentry.init({
beforeSend(event) {
const QIANKUN_INJECTED_CODE = 'window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){;';
const INJECTED_CODE_LENGTH = QIANKUN_INJECTED_CODE.length;
// adjust colno ...
},
});Universal solution: cross‑framework stack correction
A generic library intercepts error events, adjusts line/column numbers based on configured offsets, and overwrites the original stack.
Usage
Simple API works for Vue and React.
// Vue project
import StackFix from 'xxx/vue';
Vue.use(StackFix, {});
// React project
import StackFix from 'xxx';
StackFix.init();Core implementation logic
Two parts: error interception and stack correction.
Error interception
React: listen to global error and unhandledrejection events.
private errorEventListener() {
window.addEventListener('error', (event) => {
this.handleErrorIfNeeded(event.error);
});
window.addEventListener('unhandledrejection', (event) => {
this.handleErrorIfNeeded(event.reason);
});
}Vue: override Vue.config.errorHandler without breaking existing logic.
export default {
install(Vue, options = {}) {
Vue.config.errorHandler = function (error, vm, info) {
StackFix.handleErrorIfNeeded(error);
};
StackFix.init({ framework: 'vue2', ...options });
StackFix.errorEventListener();
},
};Stack correction
The core parses the stack string, applies line/column adjustments, and replaces the original stack property.
validateAndAdjustStack(stack: string) {
if (!this.microName || !stack) return '';
const defaultOptions = getDefaultMicroConfigOptions(this.framework, this.microName);
const { lineAdjustment, columnAdjustment } = {
...defaultOptions,
...(typeof this.lineAdjustment === 'number' && { lineAdjustment: this.lineAdjustment }),
...(typeof this.columnAdjustment === 'number' && { columnAdjustment: this.columnAdjustment }),
};
let newStack = '';
if (validNum(lineAdjustment)) {
newStack = skewStackLineNumbers(stack, { lineAdjustment });
}
if (validNum(columnAdjustment)) {
newStack = skewStackColumnNumber(newStack || stack, { columnAdjustment });
}
return newStack;
}Conclusion
The stack offset issue in micro‑frontends stems from framework code injection. The presented solutions enable developers to fix the problem with zero configuration for supported versions, while some cases may still require simple configuration.
Cross‑framework adaptation: supports qiankun, micro‑app, and can be extended to other micro‑frontend frameworks.
Multi‑stack compatibility: works with Vue and React projects.
Full‑scenario correction: fixes both monitoring platform stacks and console stacks.
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.
