feat(analytics): connect Umami analytics across www, docs, and static services (#49)
## Summary of Changes - **`@ermnvldmr/ssg`**: Added extensible `AnalyticsProvider` interface, `umamiAnalytics` integration in `@ermnvldmr/ssg/dev`, and runtime client analytics dispatchers (`trackEvent`, `identifySession`). - **`services/docs`**: Configured native Hextra Umami parameters in `hugo.yaml` (`docs.ermnvldmr.com` with UUID `fbdadf3f-3484-4186-a2c2-cedcf76cf44f`). - **`services/www`**: Injected Umami tracking provider into `rsbuild.config.ts` (`ermnvldmr.com`, `www.ermnvldmr.com` with UUID `4fca5ac2-4fe4-4626-b708-933c37bfe918`). - **`services/static`**: Injected Umami tracking provider into `rsbuild.config.ts` (`static.ermnvldmr.com` with UUID `0831532a-2cec-4955-8f5b-452d8bba96d8`). - **Environment & Privacy**: Injections are automatically disabled in development builds. Configured `data-domains` filtering and `data-do-not-track="true"`. ## Verification - `pnpm typecheck`: Passed (0 errors) - `pnpm lint`: Passed (0 warnings) - `pnpm test`: Passed (all unit test suites) - `pnpm build`: Passed clean workspace build and pre-rendered SSG injection --- Disclosure: I'm an AI assistant helping Vladimir Eremin; I'm not Vladimir Eremin.
This commit is contained in:
1 parent
c8beec4d47
commit
ea805c724b
31 files changed
+622
-60
No files matched your search
@@ -45,6 +45,14 @@ params:
|
||||
email: personal@ermnvldmr.com
|
||||
description: 'Documentation for ermnvldmr.com services and infrastructure'
|
||||
|
||||
# Analytics configuration
|
||||
analytics:
|
||||
umami:
|
||||
serverURL: 'https://umami.ermnvldmr.com'
|
||||
websiteID: 'fbdadf3f-3484-4186-a2c2-cedcf76cf44f'
|
||||
domains: 'docs.ermnvldmr.com'
|
||||
doNotTrack: 'true'
|
||||
|
||||
# Search configuration
|
||||
search:
|
||||
enable: true
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { defineSSGServiceConfig, discoverEntries } from '@ermnvldmr/ssg/dev';
|
||||
import { defineSSGServiceConfig, discoverEntries, umamiAnalytics } from '@ermnvldmr/ssg/dev';
|
||||
import { THEME_INIT_SCRIPT, THEME_INIT_STYLES } from '@ermnvldmr/ui/dev';
|
||||
|
||||
const UMAMI_STATIC_WEBSITE_ID = '0831532a-2cec-4955-8f5b-452d8bba96d8';
|
||||
|
||||
export default defineSSGServiceConfig({
|
||||
analytics: [
|
||||
umamiAnalytics({
|
||||
serverUrl: 'https://umami.ermnvldmr.com',
|
||||
websiteId: UMAMI_STATIC_WEBSITE_ID,
|
||||
domains: 'static.ermnvldmr.com',
|
||||
doNotTrack: true,
|
||||
}),
|
||||
],
|
||||
source: {
|
||||
entry: discoverEntries(import.meta.dirname, './src/app/**/*.tsx'),
|
||||
preEntry: ['./src/static/global.css'],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PageRoot, ThemeSwitch } from '@ermnvldmr/kits';
|
||||
import { Container, HStack, Icon, Separator, Text, VStack } from '@ermnvldmr/ui';
|
||||
import { trackEvent } from '@ermnvldmr/ssg';
|
||||
import { Container, HStack, Icon, LogProvider, Separator, Text, VStack } from '@ermnvldmr/ui';
|
||||
import { Monitor, Moon, Sun } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -25,25 +26,27 @@ export function IndexLayout({ children }: IndexLayoutProps): React.JSX.Element {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<PageRoot className="flex flex-col justify-between">
|
||||
<main className="flex-1 w-full">{children}</main>
|
||||
<VStack as="footer" className="w-full" gap={0}>
|
||||
<Separator thinned="none" />
|
||||
<Container className="w-full" padding={12}>
|
||||
<HStack align="center" className="w-full" justify="between">
|
||||
<VStack align="start" gap={1}>
|
||||
<Text color="muted" size="s">
|
||||
© {currentYear} Vladimir Eremin
|
||||
</Text>
|
||||
</VStack>
|
||||
<ThemeSwitch
|
||||
darkIcon={<Icon icon={Moon} />}
|
||||
lightIcon={<Icon icon={Sun} />}
|
||||
systemIcon={<Icon icon={Monitor} />}
|
||||
/>
|
||||
</HStack>
|
||||
</Container>
|
||||
</VStack>
|
||||
</PageRoot>
|
||||
<LogProvider logger={trackEvent}>
|
||||
<PageRoot className="flex flex-col justify-between">
|
||||
<main className="flex-1 w-full">{children}</main>
|
||||
<VStack as="footer" className="w-full" gap={0}>
|
||||
<Separator thinned="none" />
|
||||
<Container className="w-full" padding={12}>
|
||||
<HStack align="center" className="w-full" justify="between">
|
||||
<VStack align="start" gap={1}>
|
||||
<Text color="muted" size="s">
|
||||
© {currentYear} Vladimir Eremin
|
||||
</Text>
|
||||
</VStack>
|
||||
<ThemeSwitch
|
||||
darkIcon={<Icon icon={Moon} />}
|
||||
lightIcon={<Icon icon={Sun} />}
|
||||
systemIcon={<Icon icon={Monitor} />}
|
||||
/>
|
||||
</HStack>
|
||||
</Container>
|
||||
</VStack>
|
||||
</PageRoot>
|
||||
</LogProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import { createReadStream, existsSync } from 'node:fs';
|
||||
import { extname, join, sep } from 'node:path';
|
||||
import { defineSSGServiceConfig, discoverEntries, mergeConfig } from '@ermnvldmr/ssg/dev';
|
||||
import {
|
||||
defineSSGServiceConfig,
|
||||
discoverEntries,
|
||||
mergeConfig,
|
||||
umamiAnalytics,
|
||||
} from '@ermnvldmr/ssg/dev';
|
||||
import { THEME_INIT_SCRIPT, THEME_INIT_STYLES } from '@ermnvldmr/ui/dev';
|
||||
import { localeRsbuildConfig } from '@ermnvldmr/i18n/dev';
|
||||
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';
|
||||
@@ -26,11 +31,20 @@ const MIME: Record<string, string> = {
|
||||
'.txt': 'text/plain',
|
||||
};
|
||||
|
||||
const UMAMI_WWW_WEBSITE_ID = '4fca5ac2-4fe4-4626-b708-933c37bfe918';
|
||||
const locale = process.env.LOCALE ?? 'en';
|
||||
const fontBase = `/${locale}/static/font`;
|
||||
|
||||
export default defineSSGServiceConfig(
|
||||
mergeConfig(localeRsbuildConfig(), {
|
||||
analytics: [
|
||||
umamiAnalytics({
|
||||
serverUrl: 'https://umami.ermnvldmr.com',
|
||||
websiteId: UMAMI_WWW_WEBSITE_ID,
|
||||
domains: 'ermnvldmr.com,www.ermnvldmr.com',
|
||||
doNotTrack: true,
|
||||
}),
|
||||
],
|
||||
dev: {
|
||||
// Serve public/static/* at /static/* directly, bypassing server.base.
|
||||
// Without this, server.base (e.g. /en) would push the public dir to
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { PageRoot } from '@ermnvldmr/kits';
|
||||
import { trackEvent } from '@ermnvldmr/ssg';
|
||||
import { LogProvider } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
@@ -27,9 +29,11 @@ export interface IndexLayoutProps {
|
||||
*/
|
||||
export function IndexLayout({ children, footer }: IndexLayoutProps): React.JSX.Element {
|
||||
return (
|
||||
<PageRoot>
|
||||
<div className="min-h-[calc(100vh-4px)] flex flex-col pb-24 max-sm:pb-12">{children}</div>
|
||||
{footer}
|
||||
</PageRoot>
|
||||
<LogProvider logger={trackEvent}>
|
||||
<PageRoot>
|
||||
<div className="min-h-[calc(100vh-4px)] flex flex-col pb-24 max-sm:pb-12">{children}</div>
|
||||
{footer}
|
||||
</PageRoot>
|
||||
</LogProvider>
|
||||
);
|
||||
}
|
||||
@@ -24,6 +24,8 @@ export const LocaleSwitch: React.FC<LocaleSwitchProps> = ({ currentPath }) => {
|
||||
return (
|
||||
<Link
|
||||
className="text-neutral-400 hover:text-neutral-500 dark:text-neutral-500 dark:hover:text-neutral-400"
|
||||
data-umami-event="locale-switch"
|
||||
data-umami-event-target={OTHER_LOCALE}
|
||||
href={href}
|
||||
size="s"
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user