Skip to content

Commit 6954056

Browse files
feat: Analytics scroll depth (#12079)
* use Next.js integration for Plausible * feat: report scroll depth to Plausible * [getsentry/action-github-commit] Auto commit * use the more supported 'scroll' event instead of 'scrollend' * assume 100% progress beyound 95% * don't track scroll depth on short (non scrollable) pages * remove Plausible local dev setup * adjust debounce logic * [getsentry/action-github-commit] Auto commit --------- Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent f9938e4 commit 6954056

File tree

6 files changed

+98
-19
lines changed

6 files changed

+98
-19
lines changed

app/layout.tsx

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Theme} from '@radix-ui/themes';
44
import type {Metadata} from 'next';
55
import {Rubik} from 'next/font/google';
66
import Script from 'next/script';
7+
import PlausibleProvider from 'next-plausible';
78

89
import {ThemeProvider} from 'sentry-docs/components/theme-provider';
910

@@ -31,6 +32,9 @@ export const metadata: Metadata = {
3132
export default function RootLayout({children}: {children: React.ReactNode}) {
3233
return (
3334
<html lang="en" suppressHydrationWarning>
35+
<head>
36+
<PlausibleProvider domain="docs.sentry.io,rollup.sentry.io" />
37+
</head>
3438
<body className={rubik.variable} suppressHydrationWarning>
3539
<ThemeProvider
3640
attribute="class"
@@ -42,26 +46,20 @@ export default function RootLayout({children}: {children: React.ReactNode}) {
4246
{children}
4347
</Theme>
4448
</ThemeProvider>
49+
<Script
50+
async
51+
src="https://widget.kapa.ai/kapa-widget.bundle.js"
52+
data-website-id="cac7cc70-969e-4bc1-a968-55534a839be4"
53+
data-button-hide // do not render kapa ai button
54+
data-modal-override-open-class="kapa-ai-class" // all elements with this class will open the kapa ai modal
55+
data-project-name="Sentry"
56+
data-project-color="#6A5FC1"
57+
data-project-logo="https://avatars.githubusercontent.com/u/1396951?s=280&v=4"
58+
data-font-family="var(--font-rubik)"
59+
data-modal-disclaimer="Disclaimer: Welcome to our knowledge search bot! While we'd love to be able to answer all your questions, please remember this is a tool for searching our publicly available sources and not a support forum. Don't include any sensitive or personal information in your queries. For more on how Sentry handles your data, see our [Privacy Policy](https://sentry.io/privacy/). This form is protected by reCAPTCHA. Google's Privacy Policy and Google's Terms of Service apply."
60+
data-modal-example-questions="How to set up Sentry for Next.js?,What are tracePropagationTargets?"
61+
/>
4562
</body>
46-
<Script
47-
defer
48-
data-domain="docs.sentry.io,rollup.sentry.io"
49-
data-api="https://plausible.io/api/event"
50-
src="https://plausible.io/js/script.tagged-events.js"
51-
/>
52-
<Script
53-
async
54-
src="https://widget.kapa.ai/kapa-widget.bundle.js"
55-
data-website-id="cac7cc70-969e-4bc1-a968-55534a839be4"
56-
data-button-hide // do not render kapa ai button
57-
data-modal-override-open-class="kapa-ai-class" // all elements with this class will open the kapa ai modal
58-
data-project-name="Sentry"
59-
data-project-color="#6A5FC1"
60-
data-project-logo="https://avatars.githubusercontent.com/u/1396951?s=280&v=4"
61-
data-font-family="var(--font-rubik)"
62-
data-modal-disclaimer="Disclaimer: Welcome to our knowledge search bot! While we'd love to be able to answer all your questions, please remember this is a tool for searching our publicly available sources and not a support forum. Don't include any sensitive or personal information in your queries. For more on how Sentry handles your data, see our [Privacy Policy](https://sentry.io/privacy/). This form is protected by reCAPTCHA. Google's Privacy Policy and Google's Terms of Service apply."
63-
data-modal-example-questions="How to set up Sentry for Next.js?,What are tracePropagationTargets?"
64-
/>
6563
</html>
6664
);
6765
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"micromark": "^4.0.0",
7272
"next": "15.1.2",
7373
"next-mdx-remote": "^4.4.1",
74+
"next-plausible": "^3.12.4",
7475
"next-themes": "^0.3.0",
7576
"nextjs-toploader": "^1.6.6",
7677
"parse-numeric-range": "^1.3.0",

src/components/docPage/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {PaginationNav} from '../paginationNav';
1919
import {PlatformSdkDetail} from '../platformSdkDetail';
2020
import {Sidebar} from '../sidebar';
2121
import {TableOfContents} from '../tableOfContents';
22+
import {ReaderDepthTracker} from '../track-reader-depth';
2223

2324
type Props = {
2425
children: ReactNode;
@@ -114,6 +115,7 @@ export function DocPage({
114115
</main>
115116
</section>
116117
<Mermaid />
118+
<ReaderDepthTracker />
117119
</div>
118120
);
119121
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use client';
2+
import {useEffect} from 'react';
3+
import {usePlausible} from 'next-plausible';
4+
5+
import {debounce} from 'sentry-docs/utils';
6+
7+
const EVENT = 'Read Progress';
8+
const milestones = [25, 50, 75, 100] as const;
9+
type Milestone = (typeof milestones)[number];
10+
type EVENT_PROPS = {page: string; readProgress: Milestone};
11+
12+
export function ReaderDepthTracker() {
13+
const plausible = usePlausible<{[EVENT]: EVENT_PROPS}>();
14+
15+
const sendProgressToPlausible = (progress: Milestone) => {
16+
plausible(EVENT, {props: {readProgress: progress, page: document.title}});
17+
};
18+
19+
useEffect(() => {
20+
const reachedMilestones = new Set<Milestone>();
21+
22+
const trackProgress = () => {
23+
// calculate the progress based on the scroll position
24+
const scrollPosition = window.scrollY;
25+
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
26+
let progress = Math.floor((scrollPosition / totalHeight) * 100);
27+
// it's hard to trigger the 100% milestone, so we'll just assume beyond 95%
28+
if (progress > 95) {
29+
progress = 100;
30+
}
31+
32+
// find the biggest milestone that has not been reached yet
33+
const milestone = milestones.findLast(
34+
m =>
35+
progress >= m &&
36+
!reachedMilestones.has(m) &&
37+
// we shouldn't report smaller milestones once a bigger one has been reached
38+
Array.from(reachedMilestones).every(r => m > r)
39+
);
40+
if (milestone) {
41+
reachedMilestones.add(milestone);
42+
sendProgressToPlausible(milestone);
43+
}
44+
};
45+
46+
// if the page is not scrollable, we don't need to track anything
47+
if (document.documentElement.scrollHeight - window.innerHeight === 0) {
48+
return () => {};
49+
}
50+
const debouncedTrackProgress = debounce(trackProgress, 50);
51+
52+
window.addEventListener('scroll', debouncedTrackProgress);
53+
return () => {
54+
window.removeEventListener('scroll', debouncedTrackProgress);
55+
};
56+
});
57+
// do not render anything
58+
return null;
59+
}

src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,17 @@ export const isLocalStorageAvailable = () => typeof localStorage !== 'undefined'
106106
export const stripTrailingSlash = (url: string) => {
107107
return url.replace(/\/$/, '');
108108
};
109+
110+
/**
111+
* Debounce function to limit the number of times a function is called.
112+
* @param func The function to be debounced.
113+
* @param wait The time to wait before calling the function.
114+
* @returns A debounced function that only calls the original function after the wait time has passed.
115+
*/
116+
export function debounce<T extends unknown[]>(func: (...args: T) => void, delay: number) {
117+
let timer: ReturnType<typeof setTimeout>;
118+
return function (...args: T) {
119+
clearTimeout(timer);
120+
timer = setTimeout(() => func.apply(this, args), delay);
121+
};
122+
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9885,6 +9885,11 @@ next-mdx-remote@^4.4.1:
98859885
vfile "^5.3.0"
98869886
vfile-matter "^3.0.1"
98879887

9888+
next-plausible@^3.12.4:
9889+
version "3.12.4"
9890+
resolved "https://registry.yarnpkg.com/next-plausible/-/next-plausible-3.12.4.tgz#d0ac1d7dcbe9836b6c93e37d42b80e3661fdaa34"
9891+
integrity sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==
9892+
98889893
next-themes@^0.3.0:
98899894
version "0.3.0"
98909895
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a"

0 commit comments

Comments
 (0)