Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SubscribeModal } from "@/components/subscribe";
import { IdentifyUser } from "@/components/auth/IdentifyUser";
import { HubspotTracking } from "@/components/HubspotTracking";
import { XPixel } from "@/components/XPixel";
import { MetaPixel } from "@/components/MetaPixel";

const GA_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;

Expand Down Expand Up @@ -79,6 +80,7 @@ export default function RootLayout({
<IdentifyUser />
<HubspotTracking />
<XPixel />
<MetaPixel />
</body>
</html>
);
Expand Down
86 changes: 86 additions & 0 deletions src/components/MetaPixel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import Script from "next/script";
import { usePathname, useSearchParams } from "next/navigation";
import { Suspense, useEffect, useRef } from "react";

/* Meta (Facebook) pixel. The base snippet loads fbevents.js and reports the
initial page load with fbq('track', 'PageView'); conversions are reported
with fbq('track', '<StandardEvent>', {...}) from wherever they happen.

fbevents.js installs no history listener, so App Router navigations would
otherwise go unreported. Each route change re-fires the PageView track
(safe to repeat, and queues before the script finishes loading).

Production only, so localhost and preview deployments stay out of the ad
data. To check the pixel locally, temporarily drop the NODE_ENV guard. */

const META_PIXEL_ID = "1782353696227901";

declare global {
interface Window {
fbq?: (...args: unknown[]) => void;
}
}

function PageViewTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
// last path reported; doubles as the initial-load marker so the first
// render (already tracked by the inline snippet) and strict-mode effect
// re-runs don't double-count
const lastPath = useRef<string | null>(null);

useEffect(() => {
const query = searchParams?.toString();
const path = query ? `${pathname}?${query}` : pathname;

if (lastPath.current === null) {
lastPath.current = path;
return;
}
if (lastPath.current === path) return;
lastPath.current = path;

window.fbq?.("track", "PageView");
}, [pathname, searchParams]);

return null;
}

export function MetaPixel() {
if (process.env.NODE_ENV !== "production") return null;

return (
<>
<Script id="meta-pixel" strategy="afterInteractive">
{`
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CSP blocks Meta event delivery

If the deployment applies public/_headers, its CSP blocks connect.facebook.net from loading fbevents.js and blocks the www.facebook.com fallback image, causing the new pixel to report no PageView events.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/MetaPixel.tsx
Line: 65

Comment:
**CSP blocks Meta event delivery**

If the deployment applies `public/_headers`, its CSP blocks `connect.facebook.net` from loading `fbevents.js` and blocks the `www.facebook.com` fallback image, causing the new pixel to report no PageView events.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

fbq('init', '${META_PIXEL_ID}');
fbq('track', 'PageView');
`}
</Script>
<noscript>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
height="1"
width="1"
style={{ display: "none" }}
alt=""
src={`https://www.facebook.com/tr?id=${META_PIXEL_ID}&ev=PageView&noscript=1`}
/>
</noscript>
{/* useSearchParams requires a Suspense boundary in the App Router */}
<Suspense fallback={null}>
<PageViewTracker />
</Suspense>
</>
);
}
Loading