appRouterRoutingInstrumentation.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { WINDOW } from '@sentry/react';
  2. import { addFetchInstrumentationHandler, browserPerformanceTimeOrigin } from '@sentry/utils';
  3. const DEFAULT_TAGS = {
  4. 'routing.instrumentation': 'next-app-router',
  5. } ;
  6. /**
  7. * Instruments the Next.js Client App Router.
  8. */
  9. // TODO(v8): Clean this function up by splitting into pageload and navigation instrumentation respectively. Also remove startTransactionCb in the process.
  10. function appRouterInstrumentation(
  11. startTransactionCb,
  12. startTransactionOnPageLoad = true,
  13. startTransactionOnLocationChange = true,
  14. startPageloadSpanCallback,
  15. startNavigationSpanCallback,
  16. ) {
  17. // We keep track of the active transaction so we can finish it when we start a navigation transaction.
  18. let activeTransaction = undefined;
  19. // We keep track of the previous location name so we can set the `from` field on navigation transactions.
  20. // This is either a route or a pathname.
  21. let prevLocationName = WINDOW.location.pathname;
  22. if (startTransactionOnPageLoad) {
  23. const transactionContext = {
  24. name: prevLocationName,
  25. op: 'pageload',
  26. origin: 'auto.pageload.nextjs.app_router_instrumentation',
  27. tags: DEFAULT_TAGS,
  28. // pageload should always start at timeOrigin (and needs to be in s, not ms)
  29. startTimestamp: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined,
  30. metadata: { source: 'url' },
  31. } ;
  32. activeTransaction = startTransactionCb(transactionContext);
  33. startPageloadSpanCallback(transactionContext);
  34. }
  35. if (startTransactionOnLocationChange) {
  36. addFetchInstrumentationHandler(handlerData => {
  37. // The instrumentation handler is invoked twice - once for starting a request and once when the req finishes
  38. // We can use the existence of the end-timestamp to filter out "finishing"-events.
  39. if (handlerData.endTimestamp !== undefined) {
  40. return;
  41. }
  42. // Only GET requests can be navigating RSC requests
  43. if (handlerData.fetchData.method !== 'GET') {
  44. return;
  45. }
  46. const parsedNavigatingRscFetchArgs = parseNavigatingRscFetchArgs(handlerData.args);
  47. if (parsedNavigatingRscFetchArgs === null) {
  48. return;
  49. }
  50. const transactionName = parsedNavigatingRscFetchArgs.targetPathname;
  51. const tags = {
  52. ...DEFAULT_TAGS,
  53. from: prevLocationName,
  54. };
  55. prevLocationName = transactionName;
  56. if (activeTransaction) {
  57. activeTransaction.end();
  58. }
  59. const transactionContext = {
  60. name: transactionName,
  61. op: 'navigation',
  62. origin: 'auto.navigation.nextjs.app_router_instrumentation',
  63. tags,
  64. metadata: { source: 'url' },
  65. } ;
  66. startTransactionCb(transactionContext);
  67. startNavigationSpanCallback(transactionContext);
  68. });
  69. }
  70. }
  71. function parseNavigatingRscFetchArgs(fetchArgs)
  72. {
  73. // Make sure the first arg is a URL object
  74. if (!fetchArgs[0] || typeof fetchArgs[0] !== 'object' || (fetchArgs[0] ).searchParams === undefined) {
  75. return null;
  76. }
  77. // Make sure the second argument is some kind of fetch config obj that contains headers
  78. if (!fetchArgs[1] || typeof fetchArgs[1] !== 'object' || !('headers' in fetchArgs[1])) {
  79. return null;
  80. }
  81. try {
  82. const url = fetchArgs[0] ;
  83. const headers = fetchArgs[1].headers ;
  84. // Not an RSC request
  85. if (headers['RSC'] !== '1') {
  86. return null;
  87. }
  88. // Prefetch requests are not navigating RSC requests
  89. if (headers['Next-Router-Prefetch'] === '1') {
  90. return null;
  91. }
  92. return {
  93. targetPathname: url.pathname,
  94. };
  95. } catch (e) {
  96. return null;
  97. }
  98. }
  99. export { appRouterInstrumentation };
  100. //# sourceMappingURL=appRouterRoutingInstrumentation.js.map