Skip to content

Commit 0a75f6c

Browse files
authored
Merge pull request #113 from devakone/develop
Release: develop → main
2 parents 4ac8bf9 + e2cc055 commit 0a75f6c

5 files changed

Lines changed: 94 additions & 36 deletions

File tree

apps/web/src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default async function RootLayout({
8888
}
8989

9090
return (
91-
<html lang="en">
91+
<html lang="en" translate="no">
9292
<body
9393
className={`${geistSans.variable} ${geistMono.variable} antialiased ${wrappedTheme.background}`}
9494
>

apps/web/src/components/PlausibleProvider.tsx

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
"use client";
22

3-
import { Suspense, useEffect, useRef } from "react";
3+
import { Suspense, useEffect } from "react";
44
import { usePathname, useSearchParams } from "next/navigation";
5+
import { initializePlausible, trackPlausible } from "@/lib/plausible";
56

67
/**
78
* Inner component that uses useSearchParams (requires Suspense boundary).
89
*/
910
function PlausibleTracker() {
1011
const pathname = usePathname();
1112
const searchParams = useSearchParams();
12-
const isInitialized = useRef(false);
13-
const initialization = useRef<Promise<void> | null>(null);
1413

15-
// Initialize Plausible once
1614
useEffect(() => {
1715
const domain = process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN;
1816

@@ -23,46 +21,32 @@ function PlausibleTracker() {
2321
return;
2422
}
2523

26-
initialization.current = import("@plausible-analytics/tracker").then(({ init }) => {
27-
if (!isInitialized.current) {
28-
init({
29-
domain,
30-
captureOnLocalhost: process.env.NEXT_PUBLIC_PLAUSIBLE_CAPTURE_LOCALHOST === "true",
31-
outboundLinks: true,
32-
fileDownloads: true,
33-
formSubmissions: true,
34-
autoCapturePageviews: false,
35-
});
36-
isInitialized.current = true;
37-
}
24+
initializePlausible({
25+
domain,
26+
captureOnLocalhost:
27+
process.env.NEXT_PUBLIC_PLAUSIBLE_CAPTURE_LOCALHOST === "true",
28+
outboundLinks: true,
29+
fileDownloads: true,
30+
formSubmissions: true,
31+
autoCapturePageviews: false,
3832
});
3933
}, []);
4034

41-
// Track page views on route changes
4235
useEffect(() => {
43-
if (!process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN || !initialization.current) {
36+
if (!process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN) {
4437
return;
4538
}
4639

47-
// Build the full URL for tracking
4840
const url = searchParams.toString()
4941
? `${pathname}?${searchParams.toString()}`
5042
: pathname;
5143

52-
// Track pageview with the current URL
53-
void initialization.current.then(async () => {
54-
const { track } = await import("@plausible-analytics/tracker");
55-
track("pageview", { url });
56-
});
44+
void trackPlausible("pageview", { url });
5745
}, [pathname, searchParams]);
5846

5947
return null;
6048
}
6149

62-
/**
63-
* Initializes Plausible Analytics and tracks page views on route changes.
64-
* Must be rendered within the app to enable tracking.
65-
*/
6650
export function PlausibleProvider() {
6751
return (
6852
<Suspense fallback={null}>
Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,43 @@
1-
import { describe, expect, it } from "vitest";
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
3+
const init = vi.fn();
4+
const track = vi.fn();
5+
6+
vi.mock("@plausible-analytics/tracker", () => ({
7+
init,
8+
track,
9+
}));
10+
11+
afterEach(() => {
12+
vi.clearAllMocks();
13+
Reflect.deleteProperty(globalThis, "window");
14+
});
215

316
describe("analytics modules", () => {
417
it("can load during server rendering without browser globals", async () => {
518
await expect(import("../analytics")).resolves.toBeDefined();
619
await expect(import("../../components/PlausibleProvider")).resolves.toBeDefined();
720
});
21+
22+
it("initializes Plausible once across repeated callers", async () => {
23+
globalThis.window = {} as Window & typeof globalThis;
24+
const { initializePlausible } = await import("../plausible");
25+
const config = { domain: "example.com" };
26+
27+
const first = initializePlausible(config);
28+
const second = initializePlausible(config);
29+
30+
expect(first).toBe(second);
31+
await first;
32+
expect(init).toHaveBeenCalledOnce();
33+
});
34+
35+
it("does not track custom events before initialization", async () => {
36+
globalThis.window = {} as Window & typeof globalThis;
37+
const { trackPlausible } = await import("../plausible");
38+
39+
await trackPlausible("signup", {});
40+
41+
expect(track).not.toHaveBeenCalled();
42+
});
843
});

apps/web/src/lib/analytics.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@ export function trackEvent(
2222
return;
2323
}
2424

25-
void import("@plausible-analytics/tracker").then(({ track }) => {
26-
track(eventName, {
27-
props,
28-
interactive: options?.interactive,
29-
revenue: options?.revenue,
30-
});
25+
void trackPlausible(eventName, {
26+
props,
27+
interactive: options?.interactive,
28+
revenue: options?.revenue,
3129
});
3230
}
3331

@@ -64,3 +62,4 @@ export const AnalyticsEvents = {
6462
} as const;
6563

6664
export type AnalyticsEvent = (typeof AnalyticsEvents)[keyof typeof AnalyticsEvents];
65+
import { trackPlausible } from "./plausible";

apps/web/src/lib/plausible.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { PlausibleConfig } from "@plausible-analytics/tracker";
2+
3+
declare global {
4+
interface Window {
5+
__vcpPlausibleInitialization?: Promise<void>;
6+
}
7+
}
8+
9+
export function initializePlausible(
10+
config: PlausibleConfig
11+
): Promise<void> | null {
12+
if (typeof window === "undefined") {
13+
return null;
14+
}
15+
16+
window.__vcpPlausibleInitialization ??= import(
17+
"@plausible-analytics/tracker"
18+
).then(({ init }) => {
19+
init(config);
20+
});
21+
22+
return window.__vcpPlausibleInitialization;
23+
}
24+
25+
export async function trackPlausible(
26+
eventName: string,
27+
options: Parameters<
28+
typeof import("@plausible-analytics/tracker").track
29+
>[1]
30+
): Promise<void> {
31+
const initialization = window.__vcpPlausibleInitialization;
32+
33+
if (!initialization) {
34+
return;
35+
}
36+
37+
await initialization;
38+
const { track } = await import("@plausible-analytics/tracker");
39+
track(eventName, options);
40+
}

0 commit comments

Comments
 (0)