Skip to content

Commit 550bb64

Browse files
authored
feat: add analytics support with use-analytics (#90)
1 parent 025f4b4 commit 550bb64

5 files changed

Lines changed: 122 additions & 13 deletions

File tree

bun.lock

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/chronicle/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"typescript": "5.9.3"
3737
},
3838
"dependencies": {
39+
"@analytics/google-analytics": "^1.1.0",
3940
"@codemirror/lang-json": "^6.0.2",
4041
"@codemirror/state": "^6.5.4",
4142
"@codemirror/theme-one-dark": "^6.1.3",
@@ -51,6 +52,7 @@
5152
"@shikijs/rehype": "^4.0.2",
5253
"@tanstack/react-query": "5.100.10",
5354
"@vitejs/plugin-react": "^6.0.1",
55+
"analytics": "^0.8.19",
5456
"chalk": "^5.6.2",
5557
"class-variance-authority": "^0.7.1",
5658
"codemirror": "^6.0.2",
@@ -79,6 +81,7 @@
7981
"std-env": "^4.1.0",
8082
"unified": "^11.0.5",
8183
"unist-util-visit": "^5.1.0",
84+
"use-analytics": "^1.1.0",
8285
"vite": "8.0.3",
8386
"yaml": "^2.8.2",
8487
"zod": "^4.3.6"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useEffect, useState } from 'react'
2+
import { useLocation } from 'react-router'
3+
import type { ReactNode } from 'react'
4+
import type { AnalyticsConfig } from '@/types'
5+
import type { AnalyticsInstance } from 'analytics'
6+
7+
function PageViewTracker({ analytics }: { analytics: AnalyticsInstance }) {
8+
const { pathname } = useLocation()
9+
10+
useEffect(() => {
11+
try { analytics.page() } catch { /* noop */ }
12+
}, [pathname, analytics])
13+
14+
return null
15+
}
16+
17+
export function AnalyticsProvider({
18+
config,
19+
appName,
20+
children,
21+
}: {
22+
config: AnalyticsConfig
23+
appName: string
24+
children: ReactNode
25+
}) {
26+
const [analytics, setAnalytics] = useState<AnalyticsInstance | null>(null)
27+
28+
useEffect(() => {
29+
if (!config.enabled) {
30+
setAnalytics(null)
31+
return
32+
}
33+
34+
let cancelled = false
35+
36+
const init = async () => {
37+
try {
38+
const plugins: unknown[] = []
39+
if (config.googleAnalytics?.measurementId) {
40+
const { default: googleAnalytics } = await import('@analytics/google-analytics')
41+
plugins.push(
42+
googleAnalytics({
43+
measurementIds: [config.googleAnalytics.measurementId],
44+
})
45+
)
46+
}
47+
const { default: Analytics } = await import('analytics')
48+
if (!cancelled) setAnalytics(Analytics({ app: appName, plugins }))
49+
} catch {
50+
if (!cancelled) setAnalytics(null)
51+
}
52+
}
53+
54+
void init()
55+
return () => { cancelled = true }
56+
}, [config.enabled, config.googleAnalytics?.measurementId, appName])
57+
58+
return (
59+
<>
60+
{analytics && <PageViewTracker analytics={analytics} />}
61+
{children}
62+
</>
63+
)
64+
}

packages/chronicle/src/server/App.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import '@raystack/apsara/style.css';
33
import { ThemeProvider, Skeleton, Flex } from '@raystack/apsara';
44
import { lazy, Suspense } from 'react';
55
import { Navigate, useLocation } from 'react-router';
6+
import { AnalyticsProvider } from '@/components/analytics/AnalyticsProvider';
67
import { Head } from '@/lib/head';
78
import { usePageContext } from '@/lib/page-context';
89
import { resolveRoute, RouteType } from '@/lib/route-resolver';
@@ -37,18 +38,20 @@ export function App() {
3738
enableSystem={themeConfig.enableSystem}
3839
forcedTheme={themeConfig.forcedTheme}
3940
>
40-
<RootHead config={config} />
41-
<Suspense fallback={<PageFallback />}>
42-
{isApi ? (
43-
<ApiLayout>
44-
<ApiPage slug={apiSlug} />
45-
</ApiLayout>
46-
) : (
47-
<DocsLayout hideSidebar={isLanding}>
48-
{isLanding ? <LandingPage /> : <DocsPage slug={docsSlug} />}
49-
</DocsLayout>
50-
)}
51-
</Suspense>
41+
<AnalyticsProvider config={config.analytics ?? { enabled: false }} appName={config.site.title}>
42+
<RootHead config={config} />
43+
<Suspense fallback={<PageFallback />}>
44+
{isApi ? (
45+
<ApiLayout>
46+
<ApiPage slug={apiSlug} />
47+
</ApiLayout>
48+
) : (
49+
<DocsLayout hideSidebar={isLanding}>
50+
{isLanding ? <LandingPage /> : <DocsPage slug={docsSlug} />}
51+
</DocsLayout>
52+
)}
53+
</Suspense>
54+
</AnalyticsProvider>
5255
</ThemeProvider>
5356
);
5457
}

packages/chronicle/src/server/vite-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ export async function createViteConfig(
132132
}
133133
},
134134
ssr: {
135-
noExternal: ['@raystack/apsara', 'dayjs', 'fumadocs-core']
135+
noExternal: ['@raystack/apsara', 'dayjs', 'fumadocs-core'],
136+
external: ['analytics', 'use-analytics', '@analytics/google-analytics'],
136137
},
137138
environments: {
138139
client: {

0 commit comments

Comments
 (0)