This commit is contained in:
2
.env
2
.env
@@ -5,7 +5,7 @@ WORDPRESS_APP_PASSWORD=DlJH 49dp fC3a Itc3 Sl7Z Wz0k'
|
|||||||
|
|
||||||
# Umami Analytics
|
# Umami Analytics
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
|
||||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL=/stats/script.js
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||||
|
|
||||||
# GlitchTip (Sentry protocol)
|
# GlitchTip (Sentry protocol)
|
||||||
SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1
|
SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ export default async function LocaleLayout({
|
|||||||
<html lang={locale} className="scroll-smooth overflow-x-hidden">
|
<html lang={locale} className="scroll-smooth overflow-x-hidden">
|
||||||
<body className="flex flex-col min-h-screen font-sans selection:bg-accent selection:text-primary-dark antialiased overflow-x-hidden">
|
<body className="flex flex-col min-h-screen font-sans selection:bg-accent selection:text-primary-dark antialiased overflow-x-hidden">
|
||||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||||
{/* Loads Umami only when NEXT_PUBLIC_UMAMI_WEBSITE_ID is set */}
|
|
||||||
<UmamiScript />
|
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-grow animate-fade-in overflow-visible">
|
<main className="flex-grow animate-fade-in overflow-visible">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
import { usePathname, useSearchParams } from 'next/navigation';
|
||||||
import { getAppServices } from '@/lib/services/create-services';
|
import { getAppServices } from '@/lib/services/create-services';
|
||||||
|
import Script from 'next/script';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AnalyticsProvider Component
|
* AnalyticsProvider Component
|
||||||
@@ -40,6 +41,17 @@ export default function AnalyticsProvider() {
|
|||||||
}
|
}
|
||||||
}, [pathname, searchParams]);
|
}, [pathname, searchParams]);
|
||||||
|
|
||||||
return null;
|
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
||||||
|
if (!websiteId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Script
|
||||||
|
id="umami-analytics"
|
||||||
|
src="/stats/script.js"
|
||||||
|
data-website-id={websiteId}
|
||||||
|
strategy="afterInteractive"
|
||||||
|
defer
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import Script from 'next/script';
|
|
||||||
|
|
||||||
interface UmamiScriptProps {
|
|
||||||
/**
|
|
||||||
* Custom website ID to override the environment variable
|
|
||||||
*/
|
|
||||||
websiteId?: string;
|
|
||||||
/**
|
|
||||||
* Custom script URL to override the environment variable
|
|
||||||
*/
|
|
||||||
scriptUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Umami Analytics Script Component
|
|
||||||
*
|
|
||||||
* Loads the Umami analytics script only when a website ID is available.
|
|
||||||
* Uses Next.js Script component with 'afterInteractive' strategy for optimal performance.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* // Uses environment variables automatically
|
|
||||||
* <UmamiScript />
|
|
||||||
*
|
|
||||||
* // Or provide custom values
|
|
||||||
* <UmamiScript websiteId="custom-id" scriptUrl="https://custom.analytics.com/script.js" />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export default function UmamiScript({ websiteId, scriptUrl }: UmamiScriptProps) {
|
|
||||||
// Use provided website ID or fall back to environment variable
|
|
||||||
const finalWebsiteId = websiteId ?? process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
|
||||||
|
|
||||||
// If no website ID is available, don't render the script
|
|
||||||
if (!finalWebsiteId) {
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
console.warn('[Umami] NEXT_PUBLIC_UMAMI_WEBSITE_ID is not set. Analytics will be disabled.');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use provided script URL or fall back to environment variable or default
|
|
||||||
const finalScriptUrl = scriptUrl ??
|
|
||||||
process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL ??
|
|
||||||
'https://analytics.infra.mintel.me/script.js';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Script
|
|
||||||
id="umami-analytics"
|
|
||||||
src={finalScriptUrl}
|
|
||||||
data-website-id={finalWebsiteId}
|
|
||||||
strategy="afterInteractive"
|
|
||||||
defer
|
|
||||||
// Add error handling for script loading failures
|
|
||||||
onError={(error) => {
|
|
||||||
console.error('[Umami] Failed to load analytics script:', error);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -71,7 +71,21 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
|||||||
*/
|
*/
|
||||||
track(eventName: string, props?: AnalyticsEventProperties) {
|
track(eventName: string, props?: AnalyticsEventProperties) {
|
||||||
if (!this.options.enabled) return;
|
if (!this.options.enabled) return;
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
// Server-side tracking via proxy
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
||||||
|
const umamiUrl = process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL?.replace('/script.js', '') || 'https://analytics.infra.mintel.me';
|
||||||
|
|
||||||
|
if (!websiteId) return;
|
||||||
|
|
||||||
|
fetch(`${umamiUrl}/api/send`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'User-Agent': 'KLZ-Server' },
|
||||||
|
body: JSON.stringify({ type: 'event', payload: { website: websiteId, name: eventName, data: props } }),
|
||||||
|
}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const umami = (window as unknown as { umami?: UmamiGlobal }).umami;
|
const umami = (window as unknown as { umami?: UmamiGlobal }).umami;
|
||||||
umami?.track?.(eventName, props);
|
umami?.track?.(eventName, props);
|
||||||
@@ -99,7 +113,21 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
|||||||
*/
|
*/
|
||||||
trackPageview(url?: string) {
|
trackPageview(url?: string) {
|
||||||
if (!this.options.enabled) return;
|
if (!this.options.enabled) return;
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
// Server-side tracking via proxy
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
||||||
|
const umamiUrl = process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL?.replace('/script.js', '') || 'https://analytics.infra.mintel.me';
|
||||||
|
|
||||||
|
if (!websiteId || !url) return;
|
||||||
|
|
||||||
|
fetch(`${umamiUrl}/api/send`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'User-Agent': 'KLZ-Server' },
|
||||||
|
body: JSON.stringify({ type: 'event', payload: { website: websiteId, url } }),
|
||||||
|
}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const umami = (window as unknown as { umami?: UmamiGlobal }).umami;
|
const umami = (window as unknown as { umami?: UmamiGlobal }).umami;
|
||||||
|
|
||||||
|
|||||||
@@ -343,14 +343,17 @@ const nextConfig = {
|
|||||||
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
|
const umamiUrl = process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL?.replace('/script.js', '') || 'https://analytics.infra.mintel.me';
|
||||||
|
const glitchtipUrl = process.env.SENTRY_DSN ? new URL(process.env.SENTRY_DSN).origin : 'https://errors.infra.mintel.me';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/stats/:path*',
|
source: '/stats/:path*',
|
||||||
destination: 'https://analytics.infra.mintel.me/:path*',
|
destination: `${umamiUrl}/:path*`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: '/errors/:path*',
|
source: '/errors/:path*',
|
||||||
destination: 'https://errors.infra.mintel.me/:path*',
|
destination: `${glitchtipUrl}/:path*`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as Sentry from '@sentry/nextjs';
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
|
||||||
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN || 'https://c10957d0182245b1a2a806ac2d34a197@klz-cables.com/errors/1';
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn,
|
dsn,
|
||||||
|
tunnel: '/errors/tunnel',
|
||||||
enabled: Boolean(dsn),
|
enabled: Boolean(dsn),
|
||||||
tracesSampleRate: 0,
|
tracesSampleRate: 0,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user