Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Link, Outlet, RouterProvider, createRootRoute, createRoute, createRouter } from '@tanstack/solid-router';
import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
redirect,
} from '@tanstack/solid-router';
import * as Sentry from '@sentry/solid';
import { tanstackRouterBrowserTracingIntegration } from '@sentry/solid/tanstackrouter';
import { render } from 'solid-js/web';
Expand All @@ -24,6 +32,11 @@ const rootRoute = createRootRoute({
Post 2
</Link>
</li>
<li>
<Link to="/redirect" id="redirect-link">
Redirect
</Link>
</li>
</ul>
<hr />
<Outlet />
Expand Down Expand Up @@ -59,7 +72,15 @@ const postIdRoute = createRoute({
},
});

const routeTree = rootRoute.addChildren([indexRoute, postsRoute.addChildren([postIdRoute])]);
const redirectRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'redirect',
beforeLoad: () => {
throw redirect({ to: '/posts/$postId', params: { postId: '1' }, replace: true });
},
});

const routeTree = rootRoute.addChildren([indexRoute, redirectRoute, postsRoute.addChildren([postIdRoute])]);

const router = createRouter({
routeTree,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,122 @@ test('sends a navigation transaction with a parameterized URL', async ({ page })
});
});

test('sends a pageload transaction named after the resolved route when a redirect is thrown on initial load', async ({
page,
}) => {
const pageloadTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === '/posts/$postId';
});

await page.goto(`/redirect`);

const pageloadTxn = await pageloadTxnPromise;

expect(pageloadTxn).toMatchObject({
contexts: {
trace: {
data: {
'sentry.source': 'route',
'sentry.origin': 'auto.pageload.solid.tanstack_router',
'sentry.op': 'pageload',
'url.path.parameter.postId': '1',
},
op: 'pageload',
origin: 'auto.pageload.solid.tanstack_router',
},
},
transaction: '/posts/$postId',
transaction_info: {
source: 'route',
},
});
});

test('sends a navigation transaction when a redirect is thrown in beforeLoad', async ({ page }) => {
const pageloadTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});

const navigationTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return (
!!transactionEvent?.transaction &&
transactionEvent.transaction === '/posts/$postId' &&
transactionEvent.contexts?.trace?.op === 'navigation'
);
});

await page.goto(`/`);
await pageloadTxnPromise;

await page.locator('#redirect-link').click();

const navigationTxn = await navigationTxnPromise;

expect(navigationTxn).toMatchObject({
contexts: {
trace: {
data: {
'sentry.source': 'route',
'sentry.origin': 'auto.navigation.solid.tanstack_router',
'sentry.op': 'navigation',
'url.path.parameter.postId': '1',
},
op: 'navigation',
origin: 'auto.navigation.solid.tanstack_router',
},
},
transaction: '/posts/$postId',
transaction_info: {
source: 'route',
},
});
});

test('sends a navigation transaction for a normal navigation that happens after a redirect', async ({ page }) => {
const pageloadTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/`);
await pageloadTxnPromise;

const redirectTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return transactionEvent.contexts?.trace?.op === 'navigation' && transactionEvent.transaction === '/posts/$postId';
});
await page.locator('#redirect-link').click();
await redirectTxnPromise;

const navigationTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return (
transactionEvent.contexts?.trace?.op === 'navigation' &&
transactionEvent.contexts?.trace?.data?.['url.path.parameter.postId'] === '2'
);
});

await page.locator('#nav-link').click();

const navigationTxn = await navigationTxnPromise;

expect(navigationTxn).toMatchObject({
contexts: {
trace: {
data: {
'sentry.source': 'route',
'sentry.origin': 'auto.navigation.solid.tanstack_router',
'sentry.op': 'navigation',
'url.path.parameter.postId': '2',
},
op: 'navigation',
origin: 'auto.navigation.solid.tanstack_router',
},
},
transaction: '/posts/$postId',
transaction_info: {
source: 'route',
},
});
});

test('sends pageload transaction with web vitals measurements', async ({ page }) => {
const transactionPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import * as Sentry from '@sentry/react';
import { Link, Outlet, RouterProvider, createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
redirect,
} from '@tanstack/react-router';
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';

Expand All @@ -20,6 +28,11 @@ const rootRoute = createRootRoute({
Post 2
</Link>
</li>
<li>
<Link to="/redirect" id="redirect-link">
Redirect
</Link>
</li>
</ul>
<hr />
<Outlet />
Expand Down Expand Up @@ -61,7 +74,15 @@ const postIdRoute = createRoute({
},
});

const routeTree = rootRoute.addChildren([indexRoute, postsRoute.addChildren([postIdRoute])]);
const redirectRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'redirect',
beforeLoad: () => {
throw redirect({ to: '/posts/$postId', params: { postId: '1' }, replace: true });
},
});

const routeTree = rootRoute.addChildren([indexRoute, redirectRoute, postsRoute.addChildren([postIdRoute])]);

const router = createRouter({ routeTree });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,123 @@ test('sends a navigation transaction with a parameterized URL', async ({ page })
]),
});
});

test('sends a pageload transaction named after the resolved route when a redirect is thrown on initial load', async ({
page,
}) => {
const pageloadTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => {
return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === '/posts/$postId';
});

// Visiting `/redirect` directly throws `redirect({ to: '/posts/$postId', params: { postId: '1' } })`
// in `beforeLoad` during the initial pageload, so the pageload span must be renamed to the target route.
await page.goto(`/redirect`);

const pageloadTxn = await pageloadTxnPromise;

expect(pageloadTxn).toMatchObject({
contexts: {
trace: {
data: {
'sentry.source': 'route',
'sentry.origin': 'auto.pageload.react.tanstack_router',
'sentry.op': 'pageload',
'url.path.params.postId': '1',
},
op: 'pageload',
origin: 'auto.pageload.react.tanstack_router',
},
},
transaction: '/posts/$postId',
transaction_info: {
source: 'route',
},
});
});

test('sends a navigation transaction when a redirect is thrown in beforeLoad', async ({ page }) => {
const pageloadTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});

const navigationTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
});

await page.goto(`/`);
await pageloadTxnPromise;

await page.locator('#redirect-link').click();

const navigationTxn = await navigationTxnPromise;

// The `/redirect` route throws `redirect({ to: '/posts/$postId', params: { postId: '1' } })` in
// `beforeLoad`, so the navigation span must be named after the resolved target route, not `/redirect`.
expect(navigationTxn).toMatchObject({
contexts: {
trace: {
data: {
'sentry.source': 'route',
'sentry.origin': 'auto.navigation.react.tanstack_router',
'sentry.op': 'navigation',
'url.path.params.postId': '1',
},
op: 'navigation',
origin: 'auto.navigation.react.tanstack_router',
},
},
transaction: '/posts/$postId',
transaction_info: {
source: 'route',
},
});
});

test('sends a navigation transaction for a normal navigation that happens after a redirect', async ({ page }) => {
const pageloadTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/`);
await pageloadTxnPromise;

// First trigger a redirect-driven navigation. Upstream (TanStack/router#3920) this leaves the
// router in a state where `onBeforeNavigate` never fires again, which previously killed all
// subsequent navigation spans.
const redirectTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => {
return transactionEvent.contexts?.trace?.op === 'navigation' && transactionEvent.transaction === '/posts/$postId';
});
await page.locator('#redirect-link').click();
await redirectTxnPromise;

// Now a plain navigation must still produce a navigation span.
const navigationTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => {
return (
transactionEvent.contexts?.trace?.op === 'navigation' &&
transactionEvent.contexts?.trace?.data?.['url.path.params.postId'] === '2'
);
});

await page.locator('#nav-link').click();

const navigationTxn = await navigationTxnPromise;

expect(navigationTxn).toMatchObject({
contexts: {
trace: {
data: {
'sentry.source': 'route',
'sentry.origin': 'auto.navigation.react.tanstack_router',
'sentry.op': 'navigation',
'url.path.params.postId': '2',
},
op: 'navigation',
origin: 'auto.navigation.react.tanstack_router',
},
},
transaction: '/posts/$postId',
transaction_info: {
source: 'route',
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<li>
<Link to="/posts/$postId" :params="{ postId: '2' }" id="nav-link"> Post 2 </Link>
</li>
<li>
<Link to="/redirect" id="redirect-link">Redirect</Link>
</li>
</ul>
</nav>
<hr />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createApp } from 'vue';
import { RouterProvider, createRoute, createRootRoute, createRouter } from '@tanstack/vue-router';
import { RouterProvider, createRoute, createRootRoute, createRouter, redirect } from '@tanstack/vue-router';
import * as Sentry from '@sentry/vue';
import { tanstackRouterBrowserTracingIntegration } from '@sentry/vue/tanstackrouter';

Expand Down Expand Up @@ -33,7 +33,15 @@ const postIdRoute = createRoute({
component: PostView,
});

const routeTree = rootRoute.addChildren([indexRoute, postsRoute.addChildren([postIdRoute])]);
const redirectRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'redirect',
beforeLoad: () => {
throw redirect({ to: '/posts/$postId', params: { postId: '1' }, replace: true });
},
});

const routeTree = rootRoute.addChildren([indexRoute, redirectRoute, postsRoute.addChildren([postIdRoute])]);

const router = createRouter({
routeTree,
Expand Down
Loading
Loading