Skip to content

✨ add Vue Router v4 view tracking#4328

Draft
mormubis wants to merge 5 commits intomainfrom
adlrb/vue-router
Draft

✨ add Vue Router v4 view tracking#4328
mormubis wants to merge 5 commits intomainfrom
adlrb/vue-router

Conversation

@mormubis
Copy link
Contributor

@mormubis mormubis commented Mar 13, 2026

Motivation

Third of five PRs. #4325 and #4327 are already merged. After this, navigating between routes automatically creates RUM views with parameterized names instead of raw URLs.

Changes

Exports a wrapped createRouter from @datadog/browser-rum-vue/vue-router-v4 as a drop-in for the standard vue-router import:

import { createRouter } from '@datadog/browser-rum-vue/vue-router-v4'

On each navigation, afterEach calls startView() with the parameterized route name (/users/:id rather than /users/42). There is one non-obvious bit: afterEach also fires for navigations that get blocked by a guard, so we filter those out. Details in a comment on the diff.

Requires vuePlugin({ router: true }) alongside this import. That sets trackViewsManually: true at init so the SDK's built-in URL tracking does not race with the router integration.

Test instructions

yarn test:unit --spec packages/rum-vue/src/domain/router/startVueRouterView.spec.ts
yarn test:unit --spec packages/rum-vue/src/domain/router/vueRouter.spec.ts

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change
  • Added e2e/integration tests
  • Updated documentation

// afterEach fires for the initial navigation when the app is mounted via app.use(router).
// In tests without mounting, an explicit router.push() is needed to trigger the hook.
router.afterEach((to, _from, failure) => {
if (failure && !isNavigationFailure(failure, NavigationFailureType.duplicated)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Vue Router's afterEach fires even for navigations blocked by a guard, not just successful ones. The failure parameter tells you why. We skip cancelled (preempted by another navigation) and aborted (guard explicitly blocked it). duplicated (same route you're already on) we let through - that's not really a failure, the URL just didn't change because it was already there.

@cit-pr-commenter-54b7da
Copy link

cit-pr-commenter-54b7da bot commented Mar 16, 2026

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 175.02 KiB 175.02 KiB 0 B 0.00%
Rum Profiler 6.16 KiB 6.16 KiB 0 B 0.00%
Rum Recorder 27.46 KiB 27.46 KiB 0 B 0.00%
Logs 56.80 KiB 56.80 KiB 0 B 0.00%
Rum Slim 130.66 KiB 130.66 KiB 0 B 0.00%
Worker 23.63 KiB 23.63 KiB 0 B 0.00%
🚀 CPU Performance
Action Name Base CPU Time (ms) Local CPU Time (ms) 𝚫%
RUM - add global context 0.0038 0.0057 +50.00%
RUM - add action 0.0125 0.0211 +68.80%
RUM - add error 0.0122 0.02 +63.93%
RUM - add timing 0.0025 0.0037 +48.00%
RUM - start view 0.0116 0.0187 +61.21%
RUM - start/stop session replay recording 0.0006 0.001 +66.67%
Logs - log message 0.0144 0.0235 +63.19%
🧠 Memory Performance
Action Name Base Memory Consumption Local Memory Consumption 𝚫
RUM - add global context 27.11 KiB 27.48 KiB +384 B
RUM - add action 50.97 KiB 50.79 KiB -184 B
RUM - add timing 26.15 KiB 26.17 KiB +27 B
RUM - add error 54.48 KiB 55.08 KiB +617 B
RUM - start/stop session replay recording 25.42 KiB 25.83 KiB +421 B
RUM - start view 461.59 KiB 461.87 KiB +287 B
Logs - log message 43.70 KiB 44.42 KiB +742 B

🔗 RealWorld

@datadog-official
Copy link

datadog-official bot commented Mar 16, 2026

⚠️ Tests

Fix all issues with BitsAI or with Cursor

⚠️ Warnings

🧪 3 Tests failed

addVueError includes component hierarchy in component_stack when instance is provided from Chrome 63.0.3239.84 (Windows 10) (Datadog) (Fix with Cursor)
TypeError: Object.fromEntries is not a function
    at getComponentsFromStubs (webpack:///node_modules/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs:290:1 <- /tmp/_karma_webpack_415792/commons.js:140229:19)
    at createInstance (webpack:///node_modules/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs:8305:1 <- /tmp/_karma_webpack_415792/commons.js:148244:24)
    at mount (webpack:///node_modules/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs:8365:1 <- /tmp/_karma_webpack_415792/commons.js:148304:14)
    at UserContext.it (webpack:///packages/rum-vue/src/domain/error/addVueError.spec.ts:36:26 <- /tmp/_karma_webpack_415792/commons.js:113159:79)
    at <Jasmine>
vue plugin › should capture vue error from app.config.errorHandler from vuePlugin.scenario.ts (Datadog) (Fix with Cursor)
vuePlugin.scenario.ts:18:13 should capture vue error from app.config.errorHandler

[chromium] › vuePlugin.scenario.ts:18:13 › vue plugin › should capture vue error from app.config.errorHandler 

    Error: page.goto: net::ERR_CONNECTION_REFUSED at http://172.27.143.187:5175/?rum-config=%7B%22applicationId%22%3A%2237fe52bf-b3d5-4ac7-ad9b-44882d479ec8%22%2C%22clientToken%22%3A%22pubf2099de38f9c85797d20d64c7d632a69%22%2C%22defaultPrivacyLevel%22%3A%22allow%22%2C%22trackResources%22%3Atrue%2C%22trackLongTasks%22%3Atrue%2C%22enableExperimentalFeatures%22%3A%5B%5D%2C%22allowUntrustedEvents%22%3Atrue%2C%22sessionReplaySampleRate%22%3A100%2C%22telemetrySampleRate%22%3A100%2C%22telemetryUsageSampleRate%22%3A100%2C%22telemetryConfigurationSampleRate%22%3A100%2C%22proxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9394%22%2C%22remoteConfigurationProxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9389%2Fconfig%22%7D&rum-context=%7B%22run_id%22%3A%221526309862%22%2C%22test_name%22%3A%22%3CPLACEHOLDER%3E%22%7D
    Call log:
      - navigating to "http://172.27.143.187:5175/?rum-config=%7B%22applicationId%22%3A%2237fe52bf-b3d5-4ac7-ad9b-44882d479ec8%22%2C%22clientToken%22%3A%22pubf2099de38f9c85797d20d64c7d632a69%22%2C%22defaultPrivacyLevel%22%3A%22allow%22%2C%22trackResources%22%3Atrue%2C%22trackLongTasks%22%3Atrue%2C%22enableExperimentalFeatures%22%3A%5B%5D%2C%22allowUntrustedEvents%22%3Atrue%2C%22sessionReplaySampleRate%22%3A100%2C%22telemetrySampleRate%22%3A100%2C%22telemetryUsageSampleRate%22%3A100%2C%22telemetryConfigurationSampleRate%22%3A100%2C%22proxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9394%22%2C%22remoteConfigurationProxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9389%2Fconfig%22%7D&rum-context=%7B%22run_id%22%3A%221526309862%22%2C%22test_name%22%3A%22%3CPLACEHOLDER%3E%22%7D", waiting until "load"


       at ../lib/framework/createTest.ts:436
...
vue plugin › should define a view name with createRouter from vuePlugin.scenario.ts (Datadog) (Fix with Cursor)
vuePlugin.scenario.ts:5:13 should define a view name with createRouter

[chromium] › vuePlugin.scenario.ts:5:13 › vue plugin › should define a view name with createRouter 

    Error: page.goto: net::ERR_CONNECTION_REFUSED at http://172.27.143.187:5175/?rum-config=%7B%22applicationId%22%3A%2237fe52bf-b3d5-4ac7-ad9b-44882d479ec8%22%2C%22clientToken%22%3A%22pubf2099de38f9c85797d20d64c7d632a69%22%2C%22defaultPrivacyLevel%22%3A%22allow%22%2C%22trackResources%22%3Atrue%2C%22trackLongTasks%22%3Atrue%2C%22enableExperimentalFeatures%22%3A%5B%5D%2C%22allowUntrustedEvents%22%3Atrue%2C%22sessionReplaySampleRate%22%3A100%2C%22telemetrySampleRate%22%3A100%2C%22telemetryUsageSampleRate%22%3A100%2C%22telemetryConfigurationSampleRate%22%3A100%2C%22proxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9391%22%2C%22remoteConfigurationProxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9362%2Fconfig%22%7D&rum-context=%7B%22run_id%22%3A%221526309862%22%2C%22test_name%22%3A%22%3CPLACEHOLDER%3E%22%7D
    Call log:
      - navigating to "http://172.27.143.187:5175/?rum-config=%7B%22applicationId%22%3A%2237fe52bf-b3d5-4ac7-ad9b-44882d479ec8%22%2C%22clientToken%22%3A%22pubf2099de38f9c85797d20d64c7d632a69%22%2C%22defaultPrivacyLevel%22%3A%22allow%22%2C%22trackResources%22%3Atrue%2C%22trackLongTasks%22%3Atrue%2C%22enableExperimentalFeatures%22%3A%5B%5D%2C%22allowUntrustedEvents%22%3Atrue%2C%22sessionReplaySampleRate%22%3A100%2C%22telemetrySampleRate%22%3A100%2C%22telemetryUsageSampleRate%22%3A100%2C%22telemetryConfigurationSampleRate%22%3A100%2C%22proxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9391%22%2C%22remoteConfigurationProxy%22%3A%22http%3A%2F%2F172.27.143.187%3A9362%2Fconfig%22%7D&rum-context=%7B%22run_id%22%3A%221526309862%22%2C%22test_name%22%3A%22%3CPLACEHOLDER%3E%22%7D", waiting until "load"


       at ../lib/framework/createTest.ts:436
...

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 69.44%
Overall Coverage: 77.27% (-0.02%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 152dccf | Docs | Datadog PR Page | Was this helpful? React with 👍/👎 or give us feedback!

@mormubis mormubis force-pushed the adlrb/vue-plugin branch 4 times, most recently from 3a8e944 to 8c710e0 Compare March 18, 2026 08:46
Base automatically changed from adlrb/vue-plugin to main March 18, 2026 16:03
@mormubis mormubis marked this pull request as ready for review March 19, 2026 15:28
@mormubis mormubis requested a review from a team as a code owner March 19, 2026 15:28
Copy link

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

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: 3ce5ece0d0

ℹ️ 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".

})
}

export function computeViewName(matched: RouteLocationMatched[]): string {
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ question : don't we need a logic to resolve "catch-all" pattern like in react with this function ?

path = substitutePathSplats(path, routeMatch.params, routeMatch === routeMatches[routeMatches.length - 1])

import { onVueInit } from '../vuePlugin'

export function startVueRouterView(matched: RouteLocationMatched[]) {
onVueInit((configuration, rumPublicApi) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💬 suggestion: For consistency with React, onVueInit could be renamed to onRumInit. The same likely applies to onVueStart.

})
}

export function computeViewName(matched: RouteLocationMatched[]): string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💬 suggestion: ‏I think we are missing a case for catch all routes(aka wildcard). For those, in React we keep the path with the params.

})
})

describe('computeViewName', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

🥜 nitpick: ‏‏It would be great to align how these tests are written across integrations. I found, using an array of cases, like in the React tests, works well.
I tried to apply the same approach for Angular. Wdyt?

@mormubis mormubis marked this pull request as draft March 20, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants