mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-11 08:34:43 -05:00
feat: integrate Umami analytics for client-side and server-side event tracking
This commit is contained in:
@@ -28,5 +28,8 @@ DB_DISABLE_SSL="true"
|
||||
RESEND_API_KEY="your-resend-api-key"
|
||||
RESEND_DOMAIN=""
|
||||
|
||||
# Analytics
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-website-id-here"
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL="https://analytics.umami.is/script.js"
|
||||
# Build tweaks
|
||||
# SKIP_ENV_VALIDATION=1
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AnimationPreferencesProvider } from "~/components/providers/animation-p
|
||||
|
||||
import { ThemeProvider } from "~/components/providers/theme-provider";
|
||||
import { ColorThemeProvider } from "~/components/providers/color-theme-provider";
|
||||
import { UmamiScript } from "~/components/analytics/umami-script";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "beenvoice - Invoicing Made Simple",
|
||||
@@ -143,6 +144,7 @@ export default function RootLayout({
|
||||
{children}
|
||||
</AnimationPreferencesProvider>
|
||||
<Toaster />
|
||||
<UmamiScript />
|
||||
</ColorThemeProvider>
|
||||
</ThemeProvider>
|
||||
</TRPCReactProvider>
|
||||
|
||||
19
src/components/analytics/umami-script.tsx
Normal file
19
src/components/analytics/umami-script.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import Script from "next/script";
|
||||
import { env } from "~/env";
|
||||
|
||||
export function UmamiScript() {
|
||||
if (!env.NEXT_PUBLIC_UMAMI_WEBSITE_ID || !env.NEXT_PUBLIC_UMAMI_SCRIPT_URL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Script
|
||||
defer
|
||||
src={env.NEXT_PUBLIC_UMAMI_SCRIPT_URL}
|
||||
data-website-id={env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -31,6 +31,8 @@ export const env = createEnv({
|
||||
*/
|
||||
client: {
|
||||
NEXT_PUBLIC_APP_URL: z.string().url().optional(),
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.string().optional(),
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: z.string().url().optional(),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -46,6 +48,8 @@ export const env = createEnv({
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
DB_DISABLE_SSL: process.env.DB_DISABLE_SSL,
|
||||
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID,
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
|
||||
65
src/server/umami.ts
Normal file
65
src/server/umami.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { env } from "~/env";
|
||||
|
||||
type UmamiPayload = {
|
||||
payload: {
|
||||
hostname: string;
|
||||
language: string;
|
||||
referrer: string;
|
||||
screen: string;
|
||||
title: string;
|
||||
url: string;
|
||||
website: string;
|
||||
name: string;
|
||||
data?: Record<string, any>;
|
||||
};
|
||||
type: "event";
|
||||
};
|
||||
|
||||
export async function trackServerEvent(
|
||||
eventName: string,
|
||||
eventData?: Record<string, any>,
|
||||
) {
|
||||
if (!env.NEXT_PUBLIC_UMAMI_WEBSITE_ID || !env.NEXT_PUBLIC_UMAMI_SCRIPT_URL) {
|
||||
console.warn("Umami not configured, skipping server-side event tracking");
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract API endpoint from script URL (e.g., https://analytics.umami.is/script.js -> https://analytics.umami.is/api/send)
|
||||
const scriptUrl = new URL(env.NEXT_PUBLIC_UMAMI_SCRIPT_URL);
|
||||
const apiUrl = `${scriptUrl.origin}/api/send`;
|
||||
|
||||
const payload: UmamiPayload = {
|
||||
payload: {
|
||||
hostname: env.NEXT_PUBLIC_APP_URL
|
||||
? new URL(env.NEXT_PUBLIC_APP_URL).hostname
|
||||
: "localhost",
|
||||
language: "en-US",
|
||||
referrer: "",
|
||||
screen: "",
|
||||
title: "Server Event",
|
||||
url: "/",
|
||||
website: env.NEXT_PUBLIC_UMAMI_WEBSITE_ID,
|
||||
name: eventName,
|
||||
data: eventData,
|
||||
},
|
||||
type: "event",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to send Umami event:", await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending Umami event:", error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user