Micro Frontends

Learn how to identify the source of errors and route events to different Sentry projects when using micro frontends or module federation.

If your app uses micro frontends, it’s very useful to be able to track which one an error is coming from. To do this with Sentry, you can create either an automatic or a manual setup where you send events to separate Sentry projects representing each of your micro frontends. This makes it easier to see what’s going wrong and where, helping you track issues and fix them faster, especially in complex frontend architectures.

Below you'll find setup instructions for both an automatic and a manual way to route errors to different Sentry projects.

In all cases Sentry.init() must never be called more than once, doing so will result in undefined behavior.

ModuleMetadata and makeMultiplexedTransport can be used together to automatically route events to specific Sentry projects that represent your micro frontend services. Events will be routed once the service where the error occurred has been identified, ensuring errors are tracked in the correct project.

  • Requires version 2.18.0 or higher of @sentry/webpack-plugin, @sentry/rollup-plugin, @sentry/vite-plugin or @sentry/esbuild-plugin.

  • Requires SDK version 7.59.0 or higher.

To identify the source of an error, you must first inject metadata that helps identify which bundles were responsible for the error. You can do this with any of the Sentry bundler plugins by enabling the moduleMetadata option. The example below is for Webpack, but this is also supported in Vite, Rollup, and esbuild.

Install the below code snippet in your micro frontend:

Copied
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

module.exports = {
  devtool: "source-map",
  plugins: [
    sentryWebpackPlugin({
      moduleMetadata: ({ release }) => ({
        dsn: "__MODULE_DSN__",
        release,
      }),
    }),
  ],
};

Once metadata has been injected into modules, the moduleMetadataIntegration can be used to look up that metadata and attach it to stack frames with matching file names. This metadata is then available in the beforeSend callback as the module_metadata property on each StackFrame. This can be used to identify which bundles may be responsible for an error. Once the destination is determined, you can store it as a list of DSN-release pairs in event.extra[MULTIPLEXED_TRANSPORT_EXTRA_KEY] for the multiplexed transport to reference for routing.

Install the below code snippet in your host:

Copied
import {
  init,
  makeFetchTransport,
  moduleMetadataIntegration,
  makeMultiplexedTransport,
} from "@sentry/browser";

const EXTRA_KEY = "ROUTE_TO";

const transport = makeMultiplexedTransport(makeFetchTransport, (args) => {
  const event = args.getEvent();
  if (
    event &&
    event.extra &&
    EXTRA_KEY in event.extra &&
    Array.isArray(event.extra[EXTRA_KEY])
  ) {
    return event.extra[EXTRA_KEY];
  }
  return [];
});

init({
  dsn: "__DEFAULT_DSN__",
  integrations: [moduleMetadataIntegration()],
  transport,
  beforeSend: (event) => {
    if (event?.exception?.values?.[0].stacktrace.frames) {
      const frames = event.exception.values[0].stacktrace.frames;
      // Find the last frame with module metadata containing a DSN
      const routeTo = frames
        .filter(
          (frame) => frame.module_metadata && frame.module_metadata.dsn,
        )
        .map((v) => v.module_metadata)
        .slice(-1); // using top frame only - you may want to customize this according to your needs

      if (routeTo.length) {
        event.extra = {
          ...event.extra,
          [EXTRA_KEY]: routeTo,
        };
      }
    }

    return event;
  },
});

Once this is set up, errors - both handled and unhandled - will be automatically routed to the right project.

By default, args.getEvent returns only error events. You can match against other event types like so: args.getEvent(['event', 'transaction', 'replay_event']). This getEvent snippet will return matches for errors, transactions, and replays.

If you want more control to be able to explicitly specify the destination for each individual captureException, you can use the more advanced interface multiplexed transport offers.

Requires SDK version 7.59.0 or higher.

The example below uses a feature tag to determine which Sentry project to send the event to. If the event doesn't have a feature tag, we send it to the fallback DSN defined in Sentry.init.

Copied
import {
  captureException,
  init,
  makeFetchTransport,
  makeMultiplexedTransport,
} from "@sentry/browser";

init({
  dsn: "__FALLBACK_DSN__",
  transport: makeMultiplexedTransport(makeFetchTransport, ({ getEvent }) => {
    const event = getEvent();

    // Send to different DSNs, based on the event payload
    if (event?.tags?.feature === "cart") {
      return [{ dsn: "__CART_DSN__", release: "cart@1.0.0" }];
    } else if (event?.tags?.feature === "gallery") {
      return [{ dsn: "__GALLERY_DSN__", release: "gallery@1.2.0" }];
    } else {
      return [];
    }
  }),
});

You can then set tags/contexts on events in individual micro-frontends to decide which Sentry project to send the event to as follows:

It's important to always use a local scope when setting the tag (either as shown below or using withScope documentation ). Using a global scope, for example, through Sentry.setTag() will result in all subsequent events being routed to the same DSN regardless of where they originated.

Copied
captureException(new Error("oh no!"), (scope) => {
  scope.setTag("feature", "cart");
  return scope;
});
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").