Frontend Development 31 min read

Integrating Sentry into a qiankun Micro‑Frontend Project

This article provides a step‑by‑step guide on integrating Sentry monitoring into a qiankun micro‑frontend architecture, detailing two implementation schemes, handling source maps, error event routing, sandbox behavior, and code examples for Vue and React sub‑applications.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Integrating Sentry into a qiankun Micro‑Frontend Project

The article explains how to add Sentry error‑monitoring to a micro‑frontend project built with qiankun . It first outlines two integration schemes—one secondary recommendation and one primary recommendation—describing their principles, advantages, and drawbacks.

Scheme 1 (secondary recommendation) modifies the beforeSend and beforeSendTransaction hooks of Sentry to identify the originating sub‑application by inspecting the error stack or transaction name, then overwrites the event.release field with the appropriate sub‑app release value stored in a global window["$micro_app_release"] object. The article provides the full hook implementation and discusses limitations such as inability to distinguish unhandledrejection events.

Sentry.init({
  dsn: "xxx",
  environment: process.env.NODE_ENV,
  release: process.env.VUE_APP_RELEASE,
  beforeSend(event, hint) {
    const { originalException } = hint;
    const stacks = originalException.stack?.split("\n");
    let app;
    if (process.env.NODE_ENV === "production") {
      if (stacks[0].includes("react-app")) app = "react-ts-app";
      else if (stacks[0].includes("vue-app")) app = "vue-app";
      else if (stacks[0].includes("vue3-app")) app = "vue3-ts-app";
      else app = "master-app";
    } else {
      // dev environment detection
    }
    if (window["$micro_app_release"][app]) {
      event.release = window["$micro_app_release"][app];
    }
    return event;
  },
  beforeSendTransaction(event) {
    const releaseMap = { AppReact: "react-ts-app", AppVue: "vue-app", AppVue3: "vue3-ts-app" };
    const app = releaseMap[event.transaction];
    if (window["$micro_app_release"][app]) {
      event.release = window["$micro_app_release"][app];
    }
    return event;
  },
  tracesSampleRate: 1.0,
});

The article then shows how to capture Vue sub‑application errors by passing a sentryInitForVueSubApp function from the main app to each sub‑app via props, and demonstrates the required Vue and React initialization code.

// Main app sends init function to sub‑app
loadMicroApp({
  name: "vue3 app",
  entry: `//${location.host}/vue3-app/`,
  container: "#app-vue3",
  activeRule: "/app-vue3/index",
  props: { sentryInit: sentryInitForVueSubApp },
});

// Vue2 sub‑app mount
export async function mount(props) {
  props.sentryInit?.(Vue, { tracesSampleRate: 1.0, logErrors: true, attachProps: true });
  instance = new Vue({}).$mount(props.container ? props.container.querySelector("#app") : "#app");
}

// Vue3 sub‑app mount
export async function mount(props) {
  const { container, sentryInit } = props;
  instance = createApp(App).use(pinia);
  sentryInit?.(instance, { tracesSampleRate: 1.0, logErrors: true, attachProps: true });
  instance.mount(container ? container.querySelector("#app") : "#app");
}

Scheme 2 (primary recommendation) creates separate Sentry Client and Hub instances for the main app and each sub‑app. A utility usingSentryHub switches the active hub based on the current route, allowing only one hub to send events at a time. The article provides the implementation of usingSentryHub , initVueSentryHub , and initReactSentryHub , as well as the router hook that triggers hub changes.

let currentHubName;
const hubMap = {};
export function usingSentryHub(type, name, settings) {
  if (name === currentHubName) return;
  if (hubMap[name]) {
    makeMain(hubMap[name]);
    currentHubName = name;
  } else if (settings) {
    switch (type) {
      case "vue":
        hubMap[name] = initVueSentryHub(settings);
        break;
      case "react":
        hubMap[name] = initReactSentryHub(settings);
        break;
    }
    makeMain(hubMap[name]);
    currentHubName = name;
  }
}

function initVueSentryHub({ Vue, router, options, VueOptions }) {
  const integrations = [...defaultIntegrations];
  if (router) {
    integrations.push(new BrowserTracing({ routingInstrumentation: vueRouterInstrumentation(router) }));
  }
  const client = new BrowserClient({ ...options, integrations, transport: makeFetchTransport, stackParser: defaultStackParser, tracesSampleRate: 1.0 });
  attachErrorHandler(Vue, { attachProps: true, logErrors: true, tracesSampleRate: 1.0, ...VueOptions });
  if ("tracesSampleRate" in VueOptions || "tracesSampler" in VueOptions) {
    Vue.mixin(createTracingMixins({ ...VueOptions, ...VueOptions.tracingOptions }));
  }
  return new Hub(client);
}

function initReactSentryHub({ options }) {
  const client = new BrowserClient({ ...options, transport: makeFetchTransport, stackParser: defaultStackParser, integrations: [...defaultIntegrations], tracesSampleRate: 1.0 });
  return new Hub(client);
}

Sub‑applications communicate their Sentry configuration to the main app using native dispatchEvent + CustomEvent , which the main app listens for and calls usingSentryHub to create or switch to the appropriate hub.

// Sub‑app sends configuration
window.dispatchEvent(new CustomEvent("micro-app-dispatch", {
  detail: {
    type: "SET_MICRO_APP_HUB",
    payload: { type: "vue", name: process.env.VUE_APP_NAME, settings: { Vue, router, options: { dsn: "xxx", release: process.env.VUE_APP_RELEASE } } }
  }
}));

// Main app receives and applies
window.addEventListener("micro-app-dispatch", e => {
  const { type, name, settings } = e.detail;
  if (type === "SET_MICRO_APP_HUB") usingSentryHub(name ? undefined : "vue", name, settings);
});

The article also covers source‑map configuration: enabling source-map or hidden-source-map in Webpack, adding the SentryWebpackPlugin to upload maps, and adjusting the beforeSend hook to correct column offsets caused by qiankun’s script wrapping (the with sandbox). The offset correction subtracts the length of the wrapper string from the last frame’s colno .

beforeSend(event) {
  event.exception.values = event.exception.values.map(item => {
    if (item.stacktrace) {
      const { stacktrace: { frames }, ...rest } = item;
      frames[frames.length - 1].colno -= "window.__TEMP_EVAL_FUNC__ = function(){;(function(window, self, globalThis){with(window){".length;
      return { ...rest, stacktrace: { frames } };
    }
    return item;
  });
  return event;
}

Finally, the article notes the limitations of both schemes (e.g., only suitable for single‑instance scenarios) and invites readers to comment or like the post.

micro-frontendqiankunSentryError TrackingSource Maps
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.