Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b877f51
feat(tanstackstart-react): Auto-instrument request middleware
nicohrubec Jan 27, 2026
40a7ba4
refactor
nicohrubec Jan 27, 2026
3660c72
update e2e tests
nicohrubec Jan 27, 2026
9a8026b
refactor
nicohrubec Jan 27, 2026
e985b3e
yarn fix
nicohrubec Jan 27, 2026
f7f4c22
refactor
nicohrubec Jan 27, 2026
092858f
isStartFile XOR isRouteFile
nicohrubec Jan 27, 2026
ce8dbc7
guard for double import
nicohrubec Jan 27, 2026
330d7f8
add double wrapping test
nicohrubec Jan 27, 2026
b262eaa
Revert "add double wrapping test"
nicohrubec Jan 27, 2026
14873c3
feat(tanstackstart-react): Auto-instrument server function middleware
nicohrubec Jan 27, 2026
a910011
yarn fix
nicohrubec Jan 27, 2026
c122987
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 28, 2026
25e86bc
refactor + simplify
nicohrubec Jan 28, 2026
1b2c92f
Add changelog entry
nicohrubec Jan 28, 2026
b94322a
update changelog
nicohrubec Jan 30, 2026
e7f8e23
.
nicohrubec Jan 30, 2026
df42f71
Merge branch 'develop' into nh/automatic-function-middleware-instrume…
nicohrubec Jan 30, 2026
40ba4ec
feat(tanstackstart-react): Add file exclude to configure middleware a…
nicohrubec Jan 27, 2026
9912468
shouldSkipFile
nicohrubec Jan 28, 2026
fc0632a
.
nicohrubec Jan 28, 2026
478e1a2
remove enabled option
nicohrubec Jan 28, 2026
07f4f30
update tests
nicohrubec Jan 28, 2026
d41a450
Merge branch 'develop' into nh/ignore-files-from-auto-instrument
nicohrubec Jan 30, 2026
57da296
use path.extname
nicohrubec Jan 30, 2026
e806bea
only ts/tsx
nicohrubec Feb 2, 2026
d9421fc
.
nicohrubec Feb 2, 2026
529a1e6
.
nicohrubec Feb 2, 2026
bfc47ee
Add changelog
nicohrubec Feb 2, 2026
555c71e
Adjust size limit
nicohrubec Feb 2, 2026
1778822
fix import ordering
nicohrubec Feb 2, 2026
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44.5 KB',
limit: '44.6 KB',
},
// Vue SDK (ESM)
{
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(tanstackstart-react): Add `exclude` option for auto-instrumented middleware ([#19007](https://github.com/getsentry/sentry-javascript/pull/19007))**

The `autoInstrumentMiddleware` option in `sentryTanstackStart()` now accepts an object with an `exclude` property to skip specific files from automatic instrumentation. Patterns can be strings (matched as substrings) or regular expressions matched against the full file path.

```ts
sentryTanstackStart({
autoInstrumentMiddleware: {
exclude: ['/routes/admin/', /\.test\.ts$/],
},
});
```

- **feat(tanstackstart-react): Auto-instrument server function middleware ([#19001](https://github.com/getsentry/sentry-javascript/pull/19001))**

The `sentryTanstackStart` Vite plugin now automatically instruments middleware in `createServerFn().middleware([...])` calls. This captures performance data without requiring manual wrapping with `wrapMiddlewaresWithSentry()`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
import { stringMatchesSomePattern } from '@sentry/core';
import * as path from 'path';
import type { Plugin } from 'vite';

type AutoInstrumentMiddlewareOptions = {
enabled?: boolean;

@nicohrubecnicohrubecJan 30, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it

debug?: boolean;
exclude?: Array<string | RegExp>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would make sense to add some defaults here. E.g. excluding the test files if this is a very common usecase

@nicohrubecnicohrubecFeb 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll think about this some more and maybe do a follow up, for the test files for instance I think the better approach for us would be to add this to the skipped file extensions early return instead of using this option

};

type WrapResult = {
Expand DownExpand Up@@ -87,25 +89,44 @@ function applyWrap(
};
}

/**
* Checks if a file should be skipped from auto-instrumentation based on exclude patterns.
*/
export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean {
// file doesn't match exclude patterns, don't skip
if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) {
return false;
}

// file matches exclude patterns, skip
if (debug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: I think this should be rather debug.log, where debug comes from @sentry/core

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I think this needs to be console.log since it happens during build not runtime

}
return true;
}
Comment thread
nicohrubec marked this conversation as resolved.

/**
* A Vite plugin that automatically instruments TanStack Start middlewares:
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
* - `middleware` arrays in `createFileRoute()` route definitions
*/
export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {
const { enabled = true, debug = false } = options;
const { debug = false, exclude } = options;

return {
name: 'sentry-tanstack-middleware-auto-instrument',
enforce: 'pre',

transform(code, id) {
if (!enabled) {
// Skip if not a TS/TSX file
const fileExtension = path.extname(id);
if (!['.ts', '.tsx'].includes(fileExtension)) {
Comment thread
nicohrubec marked this conversation as resolved.
return null;
}

// Skip if not a TS/JS file
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
// Skip if file matches exclude patterns
if (shouldSkipFile(id, exclude, debug)) {
return null;
}

Expand Down
42 changes: 34 additions & 8 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc
*/
export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
/**
* If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares.
* Configure automatic middleware instrumentation.
*
* This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry
* instrumentation to capture performance data.
* - Set to `false` to disable automatic middleware instrumentation entirely.
* - Set to `true` (default) to enable for all middleware files.
* - Set to an object with `exclude` to enable but exclude specific files.
*
* Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually
* using `wrapMiddlewaresWithSentry`.
* The `exclude` option takes an array of strings or regular expressions matched
* against the full file path. String patterns match as substrings.
*
* @default true
*
* @example
* // Disable completely
* sentryTanstackStart({ autoInstrumentMiddleware: false })
*
* @example
* // Enable with exclusions
* sentryTanstackStart({
* autoInstrumentMiddleware: {
* exclude: ['/routes/admin/', /\.test\.ts$/],
* },
* })
*/
autoInstrumentMiddleware?: boolean;
autoInstrumentMiddleware?:
| boolean
| {
exclude?: Array<string | RegExp>;
};
}

/**
Expand DownExpand Up@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P
const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
const autoInstrumentConfig = options.autoInstrumentMiddleware;
const isDisabled = autoInstrumentConfig === false;
const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined;
Comment thread
sentry[bot] marked this conversation as resolved.

if (!isDisabled) {
plugins.push(
makeAutoInstrumentMiddlewarePlugin({
debug: options.debug,
exclude: excludePatterns,
}),
);
}

// source maps
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
addSentryImport,
arrayToObjectShorthand,
makeAutoInstrumentMiddlewarePlugin,
shouldSkipFile,
wrapGlobalMiddleware,
wrapRouteMiddleware,
wrapServerFnMiddleware,
Expand All@@ -29,20 +30,13 @@ export const Route = createFileRoute('/foo')({
});
`;

it('does not instrument non-TS/JS files', () => {
it('does not instrument non-TS/TSX files', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.css');

expect(result).toBeNull();
});

it('does not instrument when enabled is false', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/start.ts');

expect(result).toBeNull();
});

it('does not instrument files without createStart or createFileRoute', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform;
const code = "export const foo = 'bar';";
Expand DownExpand Up@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] }));

consoleWarnSpy.mockRestore();
});

it('does not instrument files matching exclude patterns', () => {
const plugin = makeAutoInstrumentMiddlewarePlugin({
exclude: ['/routes/admin/'],
}) as PluginWithTransform;
const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts');
expect(result).toBeNull();
});
Comment thread
nicohrubec marked this conversation as resolved.
});

describe('wrapGlobalMiddleware', () => {
Expand DownExpand Up@@ -445,6 +447,37 @@ describe('addSentryImport', () => {
});
});

describe('shouldSkipFile', () => {
it('returns false when exclude is undefined', () => {
expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false);
});

it('returns false when exclude is empty array', () => {
expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false);
});

it('returns false when file does not match any pattern', () => {
expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false);
});

it('returns true when file matches string pattern', () => {
expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true);
});

it('returns true when file matches regex pattern', () => {
expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true);
});

it('logs debug message when skipping file', () => {
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Skipping auto-instrumentation for excluded file'),
);
consoleLogSpy.mockRestore();
});
});

describe('arrayToObjectShorthand', () => {
it('converts single identifier', () => {
expect(arrayToObjectShorthand('foo')).toBe('{ foo }');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,13 +118,44 @@ describe('sentryTanstackStart()', () => {
it('passes correct options to makeAutoInstrumentMiddlewarePlugin', () => {
sentryTanstackStart({ debug: true, sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: true });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: true,
exclude: undefined,
});
});

it('passes debug: undefined when not specified', () => {
sentryTanstackStart({ sourcemaps: { disable: true } });

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({ enabled: true, debug: undefined });
expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});

it('passes exclude patterns when autoInstrumentMiddleware is an object', () => {
const excludePatterns = ['/routes/admin/', /\.test\.ts$/];
sentryTanstackStart({
autoInstrumentMiddleware: { exclude: excludePatterns },
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: excludePatterns,
});
});

it('passes exclude: undefined when autoInstrumentMiddleware is true', () => {
sentryTanstackStart({
autoInstrumentMiddleware: true,
sourcemaps: { disable: true },
});

expect(makeAutoInstrumentMiddlewarePlugin).toHaveBeenCalledWith({
debug: undefined,
exclude: undefined,
});
});
});
});
Loading