@sentry/nextjs
Version:
Official Sentry SDK for Next.js
115 lines (112 loc) • 4.37 kB
JavaScript
import { stripUrlQueryAndFragment, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, parseBaggageHeader, browserPerformanceTimeOrigin, debug } from '@sentry/core';
import { startBrowserTracingNavigationSpan, getAbsoluteUrl, WINDOW, startBrowserTracingPageLoadSpan } from '@sentry/react';
import RouterImport from 'next/router';
import { DEBUG_BUILD } from '../../common/debug-build.js';
import { URL_TEMPLATE } from '@sentry/conventions/attributes';
const Router = RouterImport.events ? RouterImport : RouterImport.default;
const globalObject = WINDOW;
function extractNextDataTagInformation() {
let nextData;
const nextDataTag = globalObject.document.getElementById("__NEXT_DATA__");
if (nextDataTag?.innerHTML) {
try {
nextData = JSON.parse(nextDataTag.innerHTML);
} catch {
DEBUG_BUILD && debug.warn("Could not extract __NEXT_DATA__");
}
}
if (!nextData) {
return {};
}
const nextDataTagInfo = {};
const { page, query, props } = nextData;
nextDataTagInfo.route = page;
nextDataTagInfo.params = query;
if (props?.pageProps) {
nextDataTagInfo.sentryTrace = props.pageProps._sentryTraceData;
nextDataTagInfo.baggage = props.pageProps._sentryBaggage;
}
return nextDataTagInfo;
}
function pagesRouterInstrumentPageLoad(client) {
const { route, params, sentryTrace, baggage } = extractNextDataTagInformation();
const parsedBaggage = parseBaggageHeader(baggage);
let name = route || globalObject.location.pathname;
if (parsedBaggage?.["sentry-transaction"] && name === "/_error") {
name = parsedBaggage["sentry-transaction"];
name = name.replace(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\s+/i, "");
}
const origin = browserPerformanceTimeOrigin();
startBrowserTracingPageLoadSpan(
client,
{
name,
// pageload should always start at timeOrigin (and needs to be in s, not ms)
startTime: origin ? origin / 1e3 : void 0,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "pageload",
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.pageload.nextjs.pages_router_instrumentation",
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: route ? "route" : "url",
...route && { [URL_TEMPLATE]: route },
...params && { ...params }
}
},
{ sentryTrace, baggage }
);
}
function pagesRouterInstrumentNavigation(client) {
Router.events.on("routeChangeStart", (navigationTarget) => {
const strippedNavigationTarget = stripUrlQueryAndFragment(navigationTarget);
const matchedRoute = getNextRouteFromPathname(strippedNavigationTarget);
let newLocation;
let spanSource;
if (matchedRoute) {
newLocation = matchedRoute;
spanSource = "route";
} else {
newLocation = strippedNavigationTarget;
spanSource = "url";
}
startBrowserTracingNavigationSpan(
client,
{
name: newLocation,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation",
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.nextjs.pages_router_instrumentation",
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource,
...spanSource === "route" && { [URL_TEMPLATE]: newLocation }
}
},
{ url: getAbsoluteUrl(navigationTarget) }
);
});
}
function getNextRouteFromPathname(pathname) {
const pageRoutes = globalObject.__BUILD_MANIFEST?.sortedPages;
if (!pageRoutes) {
return;
}
return pageRoutes.find((route) => {
const routeRegExp = convertNextRouteToRegExp(route);
return pathname.match(routeRegExp);
});
}
function convertNextRouteToRegExp(route) {
const routeParts = route.split("/");
let optionalCatchallWildcardRegex = "";
if (routeParts[routeParts.length - 1]?.match(/^\[\[\.\.\..+\]\]$/)) {
routeParts.pop();
optionalCatchallWildcardRegex = "(?:/(.+?))?";
}
const rejoinedRouteParts = routeParts.map(
(routePart) => routePart.replace(/^\[\.\.\..+\]$/, "(.+?)").replace(/^\[.*\]$/, "([^/]+?)")
// Replace route wildcards with lazy regex wildcards
).join("/");
return new RegExp(
`^${rejoinedRouteParts}${optionalCatchallWildcardRegex}(?:/)?$`
// optional slash at the end
);
}
export { pagesRouterInstrumentNavigation, pagesRouterInstrumentPageLoad };
//# sourceMappingURL=pagesRouterRoutingInstrumentation.js.map