Micro Frontends
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.
Below we offer two approaches.
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 different Sentry projects based on the module where the error occurred.
Requires version
2.5.0
or higher of@sentry/webpack-plugin
or version2.7.0
or higher of@sentry/rollup-plugin
,@sentry/vite-plugin
or@sentry/esbuild-plugin
.Requires SDK version
7.59.0
or higher.
First, 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. The example below is for Webpack, but this is also supported in Vite, Rollup, and esbuild.
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");
module.exports = {
devtool: "source-map",
plugins: [
sentryWebpackPlugin({
/* Other plugin config */
_experiments: {
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.
In practice, here is what your Sentry initialization should look like:
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.
If, however, you would like to have more control over the routing of errors to the point where you explicitly specify the destination for each individual captureException
, you can do that with 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 does not have a feature
tag, we send it to the fallback DSN defined in Sentry.init
.
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),
});
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 is important to always use a local scope when setting the tag (either as shown below or using withScope documentation). Using a global scope e.g. through Sentry.setTag()
will result in all subsequent events being routed to the same DSN regardless of where they originate.
captureException(new Error("oh no!"), (scope) => {
scope.setTag("feature", "cart");
return scope;
});
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").