Micro Frontend Support

If your frontend includes JavaScript bundles from multiple sources with different release cycles, you may want to identify these or route events to specific projects. This is especially useful if you've set up module federation or a similar frontend architecture.

Identifying the source of errors

To identify the source of an error, you must 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 _experiments.moduleMetadata option.

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

moduleMetadata can be any serializable data or alternatively a function that returns serializable data. If you supply a function, it will be passed an object containing the org, project, and release strings.

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

module.exports = {
  devtool: "source-map",
  plugins: [
    sentryWebpackPlugin({
      /* Other plugin config */
      _experiments: {
        moduleMetadata: ({ org, project, release }) => {
          return { team: "frontend", release };
        },
      },
    }),
  ],
};

ModuleMetadata Integration

Requires SDK version 7.59.0 or higher.

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 other integrations or 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 and used to tag or route events.

Copied
import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  integrations: [Sentry.moduleMetadataIntegration()],
  beforeSend: (event) => {
    const frames = event?.exception?.values?.[0].stacktrace.frames || [];
    // Get all team names in the stack frames
    const teams = frames
      .filter((frame) => frame.module_metadata && frame.module_metadata.team)
      .map((frame) => frame.module_metadata.team);
    // If there are teams, add them as extra data to the event
    if (teams.length > 0) {
      event.extra = {
        ...event.extra,
        teams,
      };
    }

    return event;
  },
});

Sentry.captureException(new Error("oh no!"));

Routing events to different projects

Once you've identified which module or modules are likely to be responsible for an error, you may want to send these events to different Sentry projects. The multiplexed transport can route events to different Sentry projects based on the attributes on an event.

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 does not have a feature tag, we send it to the fallback DSN defined in Sentry.init.

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

interface MatchParam {
  /** The envelope to be sent */
  envelope: Envelope;
  /**
   * A function that returns an event from the envelope if one exists. You can optionally pass an array of envelope item
   * types to filter by - only envelopes matching the given types will be returned.
   *
   * @param types Defaults to ['event'] (only error events will be returned)
   */
  getEvent(types?: EnvelopeItemType[]): Event | undefined;
}

function dsnFromFeature({ getEvent }: MatchParam) {
  const event = getEvent();
  switch (event?.tags?.feature) {
    case "cart":
      return [{ dsn: "__CART_DSN__", release: "cart@1.0.0" }];
    case "gallery":
      return [{ dsn: "__GALLERY_DSN__", release: "gallery@1.2.0" }];
    default:
  }
  return [];
}

init({
  dsn: "__FALLBACK_DSN__",
  transport: makeMultiplexedTransport(makeFetchTransport, dsnFromFeature),
});

captureException(new Error("oh no!"), (scope) => {
  scope.setTag("feature", "cart");
  return scope;
});

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

makeMultiplexedTransport API

makeMultiplexedTransport takes an instance of a transport (we recommend makeFetchTransport) and a matcher function that returns an array of objects containing the DSN and optionally the release.

Copied
interface RouteTo {
  dsn: string;
  release?: string;
}

type Matcher = (param: MatchParam) => RouteTo[];

declare function makeMultiplexedTransport(
  transport: (options: TransportOptions) => Transport,
  matcher: Matcher
): (options: TransportOptions) => Transport;

The matcher function runs after all client processing (beforeSend option, event processors from integrations).

Combining ModuleMetadata and makeMultiplexedTransport

ModuleMetadata and makeMultiplexedTransport can be used together to route events to different Sentry projects based on the module where the error occurred.

Ensure your modules have injected metadata containing the project DSN and release:

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

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

Then when you initialize Sentry:

  • Add the ModuleMetadata integration so metadata is attached to stack frames
  • Add a beforeSend callback that sets an extra property with the target DSN/release
  • Create a transport that routes events when the extra property is present
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;
  },
});
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").