diff --git a/static/app/views/dashboards/components/bfcacheMetricsOnboarding.tsx b/static/app/views/dashboards/components/bfcacheMetricsOnboarding.tsx
new file mode 100644
index 000000000000..800471e32b53
--- /dev/null
+++ b/static/app/views/dashboards/components/bfcacheMetricsOnboarding.tsx
@@ -0,0 +1,52 @@
+import emptyStateImg from 'sentry-images/spot/performance-waiting-for-span.svg';
+
+import {LinkButton} from '@sentry/scraps/button';
+import {CodeBlock} from '@sentry/scraps/code';
+import {Image} from '@sentry/scraps/image';
+import {Flex, Stack} from '@sentry/scraps/layout';
+import {Heading, Text} from '@sentry/scraps/text';
+
+import {Panel} from 'sentry/components/panels/panel';
+import {t} from 'sentry/locale';
+
+const DOCS_URL =
+ 'https://docs.sentry.io/platforms/javascript/configuration/integrations/bfcache/';
+
+const CODE_SNIPPET = `import * as Sentry from '@sentry/browser';
+
+Sentry.init({
+ dsn: '__YOUR_DSN__',
+ integrations: [Sentry.bfcacheIntegration()],
+});`;
+
+export function BfcacheMetricsOnboarding() {
+ return (
+
+
+
+
+
+ {t('Monitor Back/Forward Cache')}
+
+
+
+ {t(
+ 'Track how often the browser back/forward cache (bfcache) restores your pages, why restores are blocked, and how long miss reloads take. Enable the bfcache metrics integration to start collecting data.'
+ )}
+
+
+ {CODE_SNIPPET}
+
+
+ {t('Read the Docs')}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/static/app/views/dashboards/components/prebuiltDashboardOnboardingGate.tsx b/static/app/views/dashboards/components/prebuiltDashboardOnboardingGate.tsx
index 8b880e73252f..bf71783b235c 100644
--- a/static/app/views/dashboards/components/prebuiltDashboardOnboardingGate.tsx
+++ b/static/app/views/dashboards/components/prebuiltDashboardOnboardingGate.tsx
@@ -14,6 +14,7 @@ import {NodeRuntimeMetricsOnboarding} from 'sentry/views/insights/pages/nodeRunt
import {ModuleName} from 'sentry/views/insights/types';
import {LegacyOnboarding} from 'sentry/views/performance/onboarding';
+import {BfcacheMetricsOnboarding} from './bfcacheMetricsOnboarding';
import {GenericOnboarding} from './genericOnboarding';
interface PrebuiltDashboardOnboardingGateProps {
@@ -89,6 +90,10 @@ export function PrebuiltDashboardOnboardingGate({
return ;
}
+ if (onboarding.componentId === 'bfcache-metrics') {
+ return ;
+ }
+
return ;
}
diff --git a/static/app/views/dashboards/utils/prebuiltConfigs.tsx b/static/app/views/dashboards/utils/prebuiltConfigs.tsx
index 29ef57c65061..770dcc11ad7e 100644
--- a/static/app/views/dashboards/utils/prebuiltConfigs.tsx
+++ b/static/app/views/dashboards/utils/prebuiltConfigs.tsx
@@ -12,6 +12,7 @@ import {MCP_PROMPTS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuil
import {MCP_RESOURCES_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/ai/mcpResources';
import {MCP_TOOLS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/ai/mcpTools';
import {BACKEND_OVERVIEW_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/backendOverview/backendOverview';
+import {BFCACHE_METRICS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/bfcache';
import {CACHES_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/caches/caches';
import {FRONTEND_ASSETS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/frontendAssets/frontendAssets';
import {FRONTEND_ASSETS_DETAILS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/frontendAssets/frontendAssetsDetails';
@@ -65,6 +66,7 @@ export enum PrebuiltDashboardId {
BACKEND_QUEUE_SUMMARY = 27,
BACKEND_CACHES = 28,
NODE_RUNTIME_METRICS = 29,
+ BFCACHE_METRICS = 30,
}
/** Boolean flags on Project that indicate whether telemetry data has been received. */
@@ -81,7 +83,11 @@ type OnboardingConfig =
requiredProjectFlags?: ProjectTelemetryFlag[];
}
| {
- componentId: 'agent-monitoring' | 'mcp' | 'node-runtime-metrics';
+ componentId:
+ | 'agent-monitoring'
+ | 'mcp'
+ | 'node-runtime-metrics'
+ | 'bfcache-metrics';
requiredProjectFlags: ProjectTelemetryFlag[];
// Custom onboarding component (AI Agents, MCP, Node.js Runtime Metrics)
type: 'custom';
@@ -148,4 +154,5 @@ export const PREBUILT_DASHBOARDS: Record
[PrebuiltDashboardId.BACKEND_QUEUE_SUMMARY]: QUEUE_DETAILS_PREBUILT_CONFIG,
[PrebuiltDashboardId.BACKEND_CACHES]: CACHES_PREBUILT_CONFIG,
[PrebuiltDashboardId.NODE_RUNTIME_METRICS]: NODE_RUNTIME_METRICS_PREBUILT_CONFIG,
+ [PrebuiltDashboardId.BFCACHE_METRICS]: BFCACHE_METRICS_PREBUILT_CONFIG,
};
diff --git a/static/app/views/dashboards/utils/prebuiltConfigs/bfcache.ts b/static/app/views/dashboards/utils/prebuiltConfigs/bfcache.ts
new file mode 100644
index 000000000000..232774c15052
--- /dev/null
+++ b/static/app/views/dashboards/utils/prebuiltConfigs/bfcache.ts
@@ -0,0 +1,178 @@
+import {t} from 'sentry/locale';
+import {DisplayType, WidgetType} from 'sentry/views/dashboards/types';
+import type {PrebuiltDashboard} from 'sentry/views/dashboards/utils/prebuiltConfigs';
+import {traceMetricField} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/traceMetricField';
+
+const INTERVAL = '5m';
+
+// Emitted by the browser SDK's `bfcacheIntegration`.
+const NAVIGATION = traceMetricField('sum', 'browser.bfcache.navigation', 'counter', null);
+const NOT_RESTORED = traceMetricField(
+ 'sum',
+ 'browser.bfcache.not_restored',
+ 'counter',
+ null
+);
+const RELOAD_P75 = 'p75(value,browser.bfcache.reload.duration,distribution,millisecond)';
+const RELOAD_P95 = 'p95(value,browser.bfcache.reload.duration,distribution,millisecond)';
+
+export const BFCACHE_METRICS_PREBUILT_CONFIG: PrebuiltDashboard = {
+ dateCreated: '',
+ filters: {},
+ projects: [],
+ title: 'BFCache Metrics',
+ widgets: [
+ {
+ id: 'bfcache-hits-vs-misses',
+ title: t('BFCache Hits vs Misses'),
+ displayType: DisplayType.AREA,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['browser.bfcache.outcome', NAVIGATION],
+ aggregates: [NAVIGATION],
+ columns: ['browser.bfcache.outcome'],
+ orderby: `-${NAVIGATION}`,
+ },
+ ],
+ layout: {x: 0, y: 0, w: 3, h: 2, minH: 2},
+ },
+ {
+ id: 'bfcache-outcome-trend',
+ title: t('BFCache Outcome Trend'),
+ displayType: DisplayType.LINE,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['browser.bfcache.outcome', NAVIGATION],
+ aggregates: [NAVIGATION],
+ columns: ['browser.bfcache.outcome'],
+ orderby: `-${NAVIGATION}`,
+ },
+ ],
+ layout: {x: 3, y: 0, w: 3, h: 2, minH: 2},
+ },
+ {
+ id: 'bfcache-miss-reload-duration',
+ title: t('BFCache Miss Reload Duration'),
+ displayType: DisplayType.LINE,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['sentry.transaction', RELOAD_P75, RELOAD_P95],
+ aggregates: [RELOAD_P75, RELOAD_P95],
+ columns: ['sentry.transaction'],
+ orderby: `-${RELOAD_P75}`,
+ },
+ ],
+ layout: {x: 0, y: 2, w: 6, h: 2, minH: 2},
+ },
+ {
+ id: 'bfcache-misses-by-route',
+ title: t('BFCache Misses by Route'),
+ displayType: DisplayType.CATEGORICAL_BAR,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: 'browser.bfcache.outcome:miss',
+ fields: ['sentry.transaction', NAVIGATION],
+ aggregates: [NAVIGATION],
+ columns: ['sentry.transaction'],
+ orderby: `-${NAVIGATION}`,
+ },
+ ],
+ layout: {x: 0, y: 4, w: 3, h: 3, minH: 2},
+ },
+ {
+ id: 'bfcache-not-restored-reasons',
+ title: t('Top BFCache Not-Restored Reasons'),
+ displayType: DisplayType.CATEGORICAL_BAR,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['browser.bfcache.reason', NOT_RESTORED],
+ aggregates: [NOT_RESTORED],
+ columns: ['browser.bfcache.reason'],
+ orderby: `-${NOT_RESTORED}`,
+ },
+ ],
+ layout: {x: 3, y: 4, w: 3, h: 3, minH: 2},
+ },
+ {
+ id: 'bfcache-reasons-by-route',
+ title: t('BFCache Reasons by Route'),
+ displayType: DisplayType.CATEGORICAL_BAR,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['sentry.transaction', 'browser.bfcache.reason', NOT_RESTORED],
+ aggregates: [NOT_RESTORED],
+ columns: ['sentry.transaction', 'browser.bfcache.reason'],
+ orderby: `-${NOT_RESTORED}`,
+ },
+ ],
+ layout: {x: 0, y: 7, w: 3, h: 3, minH: 2},
+ },
+ {
+ id: 'bfcache-outcome-by-browser',
+ title: t('BFCache Outcome by Browser'),
+ displayType: DisplayType.CATEGORICAL_BAR,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['browser.name', 'browser.bfcache.outcome', NAVIGATION],
+ aggregates: [NAVIGATION],
+ columns: ['browser.name', 'browser.bfcache.outcome'],
+ orderby: `-${NAVIGATION}`,
+ },
+ ],
+ layout: {x: 3, y: 7, w: 3, h: 3, minH: 2},
+ },
+ {
+ id: 'bfcache-blockers-by-frame',
+ title: t('BFCache Blockers by Frame'),
+ displayType: DisplayType.CATEGORICAL_BAR,
+ widgetType: WidgetType.TRACEMETRICS,
+ interval: INTERVAL,
+ queries: [
+ {
+ name: '',
+ conditions: '',
+ fields: ['browser.bfcache.frame', NOT_RESTORED],
+ aggregates: [NOT_RESTORED],
+ columns: ['browser.bfcache.frame'],
+ orderby: `-${NOT_RESTORED}`,
+ },
+ ],
+ layout: {x: 0, y: 10, w: 3, h: 2, minH: 2},
+ },
+ ],
+ onboarding: {
+ type: 'custom',
+ componentId: 'bfcache-metrics',
+ // bfcache is browser-only, so gate on a frontend-data proxy. There is no
+ // bfcache-specific project flag; the data only exists once the SDK's
+ // bfcacheIntegration is enabled.
+ requiredProjectFlags: ['hasInsightsVitals'],
+ },
+};