Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f39ec3d35 | |||
| 7734440b90 | |||
| 42295c3c41 |
@@ -114,6 +114,20 @@ export default async function RootLayout({
|
||||
await import("@/lib/services/create-services.server")
|
||||
).getServerAppServices();
|
||||
|
||||
// Populate analytics context with headers for high-fidelity server-side tracking
|
||||
const { headers } = await import("next/headers");
|
||||
const requestHeaders = await headers();
|
||||
|
||||
if (serverServices.analytics.setServerContext) {
|
||||
serverServices.analytics.setServerContext({
|
||||
userAgent: requestHeaders.get("user-agent") || undefined,
|
||||
language:
|
||||
requestHeaders.get("accept-language")?.split(",")[0] || undefined,
|
||||
referrer: requestHeaders.get("referer") || undefined,
|
||||
ip: requestHeaders.get("x-forwarded-for")?.split(",")[0] || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// Track server-side (initial load)
|
||||
serverServices.analytics.trackPageview("/");
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export default async function Image() {
|
||||
letterSpacing: "0.1em",
|
||||
}}
|
||||
>
|
||||
Engineering Excellence
|
||||
Technische Beratung
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export default async function Image() {
|
||||
letterSpacing: "0.1em",
|
||||
}}
|
||||
>
|
||||
Engineering Excellence
|
||||
Technische Beratung
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,9 +8,23 @@ export async function POST(req: Request) {
|
||||
const services = getServerAppServices();
|
||||
const logger = services.logger.child({ action: "contact_submission" });
|
||||
|
||||
// Set analytics context from request headers for high-fidelity server-side tracking
|
||||
// This fulfills the "server-side via nextjs proxy" requirement
|
||||
if (services.analytics.setServerContext) {
|
||||
services.analytics.setServerContext({
|
||||
userAgent: req.headers.get("user-agent") || undefined,
|
||||
language: req.headers.get("accept-language")?.split(",")[0] || undefined,
|
||||
referrer: req.headers.get("referer") || undefined,
|
||||
ip: req.headers.get("x-forwarded-for")?.split(",")[0] || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const { name, email, company, message, website } = await req.json();
|
||||
|
||||
// Track attempt
|
||||
services.analytics.track("contact-form-attempt");
|
||||
|
||||
// Honeypot check
|
||||
if (website) {
|
||||
logger.info("Spam detected (honeypot)");
|
||||
@@ -118,6 +132,11 @@ ${message}
|
||||
});
|
||||
}
|
||||
|
||||
// Track success
|
||||
services.analytics.track("contact-form-success", {
|
||||
has_company: Boolean(company),
|
||||
});
|
||||
|
||||
return NextResponse.json({ message: "Ok" });
|
||||
} catch (error) {
|
||||
logger.error("Global API Error", { error });
|
||||
|
||||
@@ -204,7 +204,7 @@ export default function Home() {
|
||||
<div className="absolute inset-0 bg-accent/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-10 pointer-events-none" />
|
||||
<Image
|
||||
src="/media/cables/hs-kabel.png"
|
||||
alt="Technical Engineering"
|
||||
alt="Technische Beratung"
|
||||
width={800}
|
||||
height={600}
|
||||
className="w-full h-[400px] md:h-[500px] object-cover hover:scale-105 transition-transform duration-700"
|
||||
|
||||
@@ -275,7 +275,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
<div className="bg-slate-950 py-6 border-t border-white/5">
|
||||
<div className="container-custom">
|
||||
<p className="text-[10px] uppercase tracking-[0.2em] text-slate-600 text-center md:text-left">
|
||||
Website developed by{" "}
|
||||
Website entwickelt von{" "}
|
||||
<a
|
||||
href="https://mintel.me"
|
||||
target="_blank"
|
||||
|
||||
@@ -73,4 +73,15 @@ export interface AnalyticsService {
|
||||
* ```
|
||||
*/
|
||||
trackPageview(url?: string): void;
|
||||
|
||||
/**
|
||||
* Set the server-side context for the current request.
|
||||
* This is used for server-side tracking (e.g. from Next.js proxy).
|
||||
*/
|
||||
setServerContext?(context: {
|
||||
userAgent?: string;
|
||||
language?: string;
|
||||
referrer?: string;
|
||||
ip?: string;
|
||||
}): void;
|
||||
}
|
||||
|
||||
@@ -68,4 +68,16 @@ export class NoopAnalyticsService implements AnalyticsService {
|
||||
trackPageview(_url?: string) {
|
||||
// intentionally noop - analytics are disabled
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op implementation of setServerContext.
|
||||
*/
|
||||
setServerContext(_context: {
|
||||
userAgent?: string;
|
||||
language?: string;
|
||||
referrer?: string;
|
||||
ip?: string;
|
||||
}) {
|
||||
// intentionally noop - analytics are disabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ export type UmamiAnalyticsServiceOptions = {
|
||||
export class UmamiAnalyticsService implements AnalyticsService {
|
||||
private websiteId?: string;
|
||||
private endpoint: string;
|
||||
private serverContext?: {
|
||||
userAgent?: string;
|
||||
language?: string;
|
||||
referrer?: string;
|
||||
ip?: string;
|
||||
};
|
||||
|
||||
constructor(private readonly options: UmamiAnalyticsServiceOptions) {
|
||||
this.websiteId = config.analytics.umami.websiteId;
|
||||
@@ -36,6 +42,19 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
||||
: "/stats";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server-side context for the current request.
|
||||
* This allows the service to use real request headers for tracking.
|
||||
*/
|
||||
setServerContext(context: {
|
||||
userAgent?: string;
|
||||
language?: string;
|
||||
referrer?: string;
|
||||
ip?: string;
|
||||
}) {
|
||||
this.serverContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to send the payload to Umami API.
|
||||
*/
|
||||
@@ -53,18 +72,37 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
||||
? `${window.screen.width}x${window.screen.height}`
|
||||
: undefined,
|
||||
language:
|
||||
typeof window !== "undefined" ? navigator.language : undefined,
|
||||
referrer: typeof window !== "undefined" ? document.referrer : undefined,
|
||||
typeof window !== "undefined"
|
||||
? navigator.language
|
||||
: this.serverContext?.language,
|
||||
referrer:
|
||||
typeof window !== "undefined"
|
||||
? document.referrer
|
||||
: this.serverContext?.referrer,
|
||||
...data,
|
||||
};
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
// Set User-Agent
|
||||
if (typeof window !== "undefined") {
|
||||
headers["User-Agent"] = navigator.userAgent;
|
||||
} else if (this.serverContext?.userAgent) {
|
||||
headers["User-Agent"] = this.serverContext.userAgent;
|
||||
} else {
|
||||
headers["User-Agent"] = "Mintel-Server-Proxy";
|
||||
}
|
||||
|
||||
// Forward client IP if available (Umami must be configured to trust this)
|
||||
if (this.serverContext?.ip) {
|
||||
headers["X-Forwarded-For"] = this.serverContext.ip;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.endpoint}/api/send`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent":
|
||||
typeof window === "undefined" ? "KLZ-Server" : navigator.userAgent,
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify({ type, payload }),
|
||||
keepalive: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Index": {
|
||||
"hero": {
|
||||
"tag": "Engineering Excellence",
|
||||
"tag": "Technische Beratung",
|
||||
"title": "Spezialisierter Partner für Energiekabelprojekte",
|
||||
"titleHighlight": "Energiekabelprojekte",
|
||||
"subtitle": "Herstellerneutrale technische Beratung für Ihre Projekte in Mittel- und Hochspannungsnetzen bis zu 110 kV.",
|
||||
@@ -31,7 +31,7 @@
|
||||
"expertise": {
|
||||
"tag": "Expertise",
|
||||
"title": "Anwendungen & Zielgruppen",
|
||||
"description": "Wir unterstützen Akteure der Energiewende bei der Realisierung komplexer Kabelprojekte mit höchster Präzision.",
|
||||
"description": "Wir unterstützen Sie bei der Realisierung Ihrer Kabelprojekte.",
|
||||
"groups": [
|
||||
"Energieversorger",
|
||||
"Ingenieurbüros",
|
||||
@@ -83,16 +83,16 @@
|
||||
"datenschutz": "Datenschutz",
|
||||
"agb": "AGB",
|
||||
"rights": "Alle Rechte vorbehalten.",
|
||||
"madeWith": "Made with",
|
||||
"precision": "precision",
|
||||
"inGermany": "in Germany"
|
||||
"madeWith": "Entwickelt mit",
|
||||
"precision": "Präzision",
|
||||
"inGermany": "in Deutschland"
|
||||
}
|
||||
},
|
||||
"About": {
|
||||
"hero": {
|
||||
"tagline": "Über uns",
|
||||
"title": "Wir gestalten die Infrastructure der Zukunft",
|
||||
"subtitle": "MB Grid Solution steht für technische Exzellenz in der Energiekabeltechnologie. Wir verstehen uns als Ihr technischer Lotse."
|
||||
"title": "Zuverlässige Begleitung für Ihre Netzinfrastruktur",
|
||||
"subtitle": "Herstellerneutrale Beratung in der Energiekabeltechnologie. Wir verstehen uns als Ihr technischer Lotse."
|
||||
},
|
||||
"intro": {
|
||||
"p1": "Unsere Wurzeln liegen in der tiefen praktischen Erfahrung unserer technischen Berater und unserer Netzwerke im globalem Kabelmarkt. Wir vereinen Tradition mit modernster Innovation, um zuverlässige Energielösungen für Projekte bis 110 kV zu realisieren.",
|
||||
|
||||
Reference in New Issue
Block a user