Skip to content

Add Datadog Browser RUM client instrumentation for Next.js App Router - #610

Open
jbeard4 wants to merge 9 commits into
mainfrom
codex/add-datadog-browser-rum-instrumentation
Open

Add Datadog Browser RUM client instrumentation for Next.js App Router#610
jbeard4 wants to merge 9 commits into
mainfrom
codex/add-datadog-browser-rum-instrumentation

Conversation

@jbeard4

@jbeard4 jbeard4 commented Mar 7, 2026

Copy link
Copy Markdown
Member

Motivation

  • Add a browser-side top-of-funnel signal so Datadog RUM sessions/page views can be correlated with Heroku Node requests, API Gateway traffic, and Lambda traces for an end-to-end traffic dashboard.
  • Follow Datadog recommendations for Next.js App Router by initializing RUM in a browser-only client component and avoid double-initialization and SSR impact.
  • Make RUM production-safe by gating on public env vars, keeping session replay disabled by default, and conservatively redacting potential sensitive strings.

Description

  • Installed @datadog/browser-rum and added a client-only initializer at src/components/datadog-rum-init.tsx that reads configuration from NEXT_PUBLIC_DATADOG_* env vars, guards against duplicate initialization, and no-ops when required IDs are missing.
  • The initializer configures recommended production settings (applicationId, clientToken, site, service, env, version, sessionSampleRate, sessionReplaySampleRate, trackUserInteractions, trackResources, trackLongTasks, defaultPrivacyLevel) and gates session replay behind NEXT_PUBLIC_DATADOG_SESSION_REPLAY_ENABLED (default off).
  • Enables distributed tracing correlation by adding allowedTracingUrls (includes window.location.origin plus a configurable NEXT_PUBLIC_DATADOG_TRACING_ORIGINS), and implements trackViewsManually plus startView with normalized route names to reduce dynamic-route cardinality.
  • Adds conservative beforeSend sanitization to strip query/hash fragments and redact email-like strings, sets app_name context, wires DatadogRumInit into the App Router root layout at src/app/layout.tsx, and documents required vars and a consent TODO in README.md.

Testing

  • Ran npm install @datadog/browser-rum to add the SDK and updated package.json and package-lock.json successfully.
  • Ran type checks with npm run check-types which passed after minor TypeScript narrowings were applied to beforeSend (type errors were resolved).
  • Ran linter with npm run lint which completed (existing unrelated lint warnings remain in other files and were not introduced by this change).
  • Attempted npm run build which failed in this environment due to repeated Google Fonts fetch errors from fonts.gstatic.com unrelated to the RUM changes, so build failure is environmental and not caused by the Datadog instrumentation.

Codex Task

@jbeard4
jbeard4 temporarily deployed to yourpeer-codex-add-data-mksfhz March 7, 2026 04:52 Inactive

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d302a22d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/datadog-rum-init.tsx Outdated
service: DATADOG_SERVICE,
context: {
app_name: DATADOG_APP_NAME,
raw_pathname: pathname,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop raw pathname from Datadog view context

View names are normalized via normalizePathnameForViewName, but raw_pathname is still attached verbatim to each startView event here. On routes with dynamic segments (IDs, tokens, or other unique slugs), this sends unsanitized high-cardinality data to Datadog and can expose sensitive path values, which defeats the redaction/cardinality controls implemented elsewhere in this change. Remove this field or sanitize it before adding it to the event context.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 16:47 — with GitHub Actions Inactive

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces browser RUM instrumentation, but it is not safe to approve yet due to a privacy risk and untested risky logic.

Blocking issues

  • Privacy/PII risk: datadogRum.startView sends raw_pathname: pathname in event context without sanitization. Path segments can contain identifiers/emails, which can leak user data to telemetry.
  • Risky logic has no tests: route normalization/sanitization (normalizePathnameForViewName, sanitizeStringValue, beforeSend) and single-init behavior are critical for privacy and telemetry correctness but are untested, leaving regression risk.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 17:07 — with GitHub Actions Inactive
@jbeard4
jbeard4 temporarily deployed to yourpeer-codex-add-data-mksfhz March 7, 2026 17:07 Inactive

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds browser RUM instrumentation, but it introduces privacy-risky telemetry behavior without sufficient safeguards/tests, so it is not safe to approve yet.

Blocking issues

  • Privacy/compliance risk: DatadogRumInit initializes RUM unconditionally for all users when tokens are present, with trackUserInteractions, trackResources, and trackLongTasks enabled, and no consent gate (src/components/datadog-rum-init.tsx). The README even notes consent is only a future TODO. This can capture user activity before consent.
  • PII leakage risk: beforeSend sanitization is incomplete (src/components/datadog-rum-init.tsx, src/components/datadog-rum-utils.ts). It only redacts query/hash + email patterns in a small subset of fields (view.url, resource.url, error.message), leaving other event fields/contexts potentially containing sensitive identifiers.
  • Risky logic lacks integration coverage: only utility-unit tests were added (src/components/datadog-rum-utils.test.ts), but there are no tests validating initialization behavior in app runtime (single init across rerenders/navigation, manual startView correctness, and privacy filtering application to emitted events). Per conservative policy, this is a blocking regression risk.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9fd03eca06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +41 to +42
const [pathWithoutQuery] = value.split("?");
return pathWithoutQuery.split("#")[0] ?? pathWithoutQuery;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid truncating full error messages during sanitization

stripQueryAndHash removes everything after the first ? and then everything after #, and sanitizeStringValue is used on error.message in beforeSend. In practice this drops critical debugging context for normal error strings containing those characters (for example, messages with private-field names like #x, or fetch errors that include a URL and then status/details after a query string), so Datadog receives an incomplete message. Consider limiting query/hash stripping to URL fields or sanitizing URL substrings inside messages instead of truncating the entire message.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 17:18 — with GitHub Actions Inactive
@jbeard4
jbeard4 temporarily deployed to yourpeer-codex-add-data-mksfhz March 7, 2026 17:18 Inactive

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog RUM wiring is close, but there are privacy-consent handling gaps that make this unsafe to approve as-is.

Blocking issues

  • Privacy/consent regression: consent is only checked during useEffect runs keyed to pathname, and after initialization there is no call to update Datadog tracking consent (for example when a user revokes consent). RUM can continue collecting/sending telemetry after opt-out. (src/components/datadog-rum-init.tsx)
  • Consent state changes are not handled reactively unless navigation occurs. If consent is granted or revoked without a route change, behavior is stale (no init when newly granted, no shutdown/update when revoked), which is an unclear and risky analytics behavior needing explicit handling/tests. (src/components/datadog-rum-init.tsx)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60753e52cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +56 to +57
if (!hasDatadogConfiguration || !hasConsent || isAlreadyInitialized) {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate consent revocation to the active RUM SDK

When consent changes from granted to denied after RUM has already been initialized, this early return path exits without updating the Datadog SDK consent state, so the already-active instance keeps collecting automatic telemetry (trackResources, trackUserInteractions, trackLongTasks) for that session. This only happens when users can revoke consent mid-session, but in that flow it violates the intended consent gate and should explicitly transition the SDK to non-granted tracking.

Useful? React with 👍 / 👎.

Comment thread src/components/datadog-rum-utils.ts Outdated
Comment on lines +106 to +108
return decodeURIComponent(
cookie.slice(cookieName.length + 1),
).toLowerCase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard consent cookie decoding against malformed values

The consent parser decodes the cookie value without error handling, so a malformed value (for example one containing an invalid % escape) throws URIError during the client consent check and interrupts RUM initialization/view tracking on that render cycle. This is input-dependent, but a single bad cookie should not break telemetry logic; wrap decoding in a safe fallback.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 17:27 — with GitHub Actions Inactive
@jbeard4
jbeard4 temporarily deployed to yourpeer-codex-add-data-mksfhz March 7, 2026 17:27 Inactive

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces useful Datadog RUM gating and tests, but it is not safe to approve yet due to privacy and runtime robustness gaps in the new sanitization/consent utilities.

Blocking issues

  • Potential PII leakage: sanitizeRumEvent stops recursion after depth 6 (MAX_SANITIZE_DEPTH in src/components/datadog-rum-utils.ts), so deeply nested event fields are left unsanitized and may still contain emails/query tokens. Since this code is intended to prevent sensitive data from being sent to Datadog, this is a blocking privacy risk.
  • Client runtime crash risk: parseCookieValue calls decodeURIComponent(...) without a try/catch (src/components/datadog-rum-utils.ts). A malformed cookie value (invalid percent-encoding) will throw URIError, which can break the DatadogRumInit effect in src/components/datadog-rum-init.tsx and cause app-level instability.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0248193a57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const NUMERIC_SEGMENT_PATTERN = /^\d+$/;
const UUID_SEGMENT_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const LONG_TOKEN_SEGMENT_PATTERN = /^[a-z0-9_-]{24,}$/i;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Narrow token heuristic to avoid static-route collisions

normalizePathnameForViewName treats any segment matching ^[a-z0-9_-]{24,}$ as :id, which also matches legitimate long static slugs (for example internal-comment-guidelines, a real route constant in this repo). In those cases, startRumView will collapse distinct static pages into the same view name (route:/:id), reducing route-level accuracy in RUM dashboards and breaking page-specific analysis even when no dynamic identifier is present.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 17:39 — with GitHub Actions Inactive
@jbeard4
jbeard4 temporarily deployed to yourpeer-codex-add-data-mksfhz March 7, 2026 17:40 Inactive

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can’t safely approve this PR yet. The Datadog RUM integration introduces privacy-risky behavior and has gaps in test coverage for critical runtime flows.

Blocking issues

  • Potential PII leakage in RUM view names: normalizePathnameForViewName only masks numeric/UUID/long-token segments, so human-readable slugs (including names or other identifiers) are sent via startRumView unchanged. This can leak user-identifying path data to Datadog.
  • Missing tests for the highest-risk logic in datadog-rum-init.tsx: there is no component-level test validating consent transitions and initialization/view-start ordering. Given this controls third-party telemetry enablement, the current unit tests are insufficient to rule out regressions.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 442d6c952b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,140 @@
"use client";

import { datadogRum } from "@datadog/browser-rum";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Lazily load Browser RUM SDK behind consent/config checks

Because @datadog/browser-rum is imported at module load and DatadogRumInit is mounted in the root layout, every client still downloads and parses the RUM SDK even when NEXT_PUBLIC_DATADOG_ENABLED is false or consent is not granted. In those common no-op paths, this adds avoidable JavaScript cost to all page loads and undercuts the runtime gating in this change; loading the SDK dynamically only after the gate passes avoids that startup regression.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 17:55 — with GitHub Actions Inactive
@jbeard4
jbeard4 had a problem deploying to yourpeer-codex-add-data-mksfhz March 7, 2026 17:56 Failure

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot safely approve due to a functional regression risk in the RUM init component that can generate duplicate view events and repeated side effects in production.

Blocking issues

  • src/components/datadog-rum-init.tsx: runtimeDeps is recreated on every render (const runtimeDeps = deps ?? createDeps()), but all three useEffect hooks depend on runtimeDeps. This makes effects rerun on any rerender, so startRumViewFn can fire duplicate startView calls even when pathname/consent did not change, and consent/init logic is re-executed unnecessarily. This can inflate telemetry, increase cost, and distort behavior.
  • Missing test coverage for the default (non-injected) dependency path: existing tests only pass a stable deps object, so they do not catch the production identity-churn issue above. Add a regression test asserting startView is not called repeatedly on rerenders without pathname/consent changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f72c0cd1ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +72 to +76
if (SAFE_VIEW_SEGMENTS.has(segment)) {
return segment;
}

return ":slug";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve canonical route segments in normalized view names

normalizePathnameForViewName falls back to :slug for every segment not in SAFE_VIEW_SEGMENTS, but the allowlist does not include several canonical static routes used by this app (for example privacy-policy and terms-of-use from src/components/common.ts). In practice, many distinct pages collapse into identical view names (e.g. /privacy-policy and /locations both normalize to route:/:slug), which degrades route-level RUM accuracy and can skew page-specific dashboards/experiments.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 18:32 — with GitHub Actions Inactive
@jbeard4
jbeard4 had a problem deploying to yourpeer-codex-add-data-mksfhz March 7, 2026 18:32 Failure

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking security/privacy/auth/data-loss issues found in the Datadog Browser RUM instrumentation diff. Consent gating, initialization guard, and event/path sanitization are present, and the added tests cover core consent/init/view and sanitization behavior.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3251112e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +110 to +112
const interval = runtimeDeps.setIntervalFn(() => {
setHasConsent(runtimeDeps.getConsentStatus());
}, CONSENT_REFRESH_INTERVAL_MS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip consent polling when RUM is disabled

This effect always starts a 1s interval even when hasDatadogConfiguration is false (for example the documented default NEXT_PUBLIC_DATADOG_ENABLED=false), so every client session still wakes up every second to re-read consent/cookies despite RUM being a no-op. In that disabled path this recurring timer does unnecessary background work on all page loads and tabs; short-circuiting the polling effect when configuration is missing avoids the avoidable CPU/battery overhead.

Useful? React with 👍 / 👎.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 18:58 — with GitHub Actions Inactive
@jbeard4
jbeard4 had a problem deploying to yourpeer-codex-add-data-mksfhz March 7, 2026 18:58 Failure

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can’t approve this safely. The new Datadog init component appears to access window during render, which can break SSR/hydration paths in Next.js.

Blocking issues

  • src/components/datadog-rum-init.tsx: useMemo(() => deps ?? createDeps(), [deps]) calls createDeps() during render, and createDeps() directly reads window (window.clearInterval, window.setInterval). In Next.js App Router, client components can be rendered on the server; this can throw ReferenceError: window is not defined and break page rendering. Move all window access into useEffect or guard with typeof window !== 'undefined' before dereferencing.

@jbeard4
jbeard4 temporarily deployed to CI_CD_PIPELINE March 7, 2026 19:13 — with GitHub Actions Inactive
@jbeard4
jbeard4 temporarily deployed to yourpeer-codex-add-data-mksfhz March 7, 2026 19:14 Inactive

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Datadog Browser RUM integration is gated behind explicit env/config checks and consent, initializes only on the client, includes a single-init guard, and adds targeted sanitization in beforeSend to reduce accidental PII leakage. The PR also includes meaningful unit tests for consent flow, initialization/start-view sequencing, route normalization, and event sanitization. No blocking security, privacy, or regression issues were identified from the provided diff.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2b3131e516

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +182 to +185
sessionReplaySampleRate: DATADOG_SESSION_REPLAY_ENABLED
? Number.isFinite(DATADOG_SESSION_REPLAY_SAMPLE_RATE)
? DATADOG_SESSION_REPLAY_SAMPLE_RATE
: 100

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail closed on invalid session replay sample rate

When NEXT_PUBLIC_DATADOG_SESSION_REPLAY_ENABLED is true and NEXT_PUBLIC_DATADOG_SESSION_REPLAY_SAMPLE_RATE is malformed (for example an empty string or non-numeric value), this branch falls back to 100, which enables replay collection for all sessions instead of defaulting to a safe disabled value. In a misconfigured deploy this can unexpectedly capture far more session replay data (and cost) than intended, so the fallback should be conservative.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant