-
Notifications
You must be signed in to change notification settings - Fork 1
X Tracking Pixel #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
X Tracking Pixel #59
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { Toaster } from "sonner"; | |
| import { SubscribeModal } from "@/components/subscribe"; | ||
| import { IdentifyUser } from "@/components/auth/IdentifyUser"; | ||
| import { HubspotTracking } from "@/components/HubspotTracking"; | ||
| import { XPixel } from "@/components/XPixel"; | ||
|
|
||
| const GA_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID; | ||
|
|
||
|
|
@@ -77,6 +78,7 @@ export default function RootLayout({ | |
| <SubscribeModal /> | ||
| <IdentifyUser /> | ||
| <HubspotTracking /> | ||
| <XPixel /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When any visitor opens a production route, the globally mounted pixel reports the initial page view without consulting a consent preference, so visitors cannot withhold or withdraw the consent promised by the privacy notice. How this was verified: The root layout mounts Prompt To Fix With AIThis is a comment left during a code review.
Path: src/app/layout.tsx
Line: 81
Comment:
**Advertising pixel bypasses consent**
When any visitor opens a production route, the globally mounted pixel reports the initial page view without consulting a consent preference, so visitors cannot withhold or withdraw the consent promised by the privacy notice. **How this was verified:** The root layout mounts `XPixel` unconditionally, its only guard checks `NODE_ENV`, and the repository contains no consent or opt-out mechanism.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Real gap, but pre-existing and out of scope for this PR. You are right that the privacy notice says "We rely on your consent, which you can withdraw" ( Gating only the X pixel would be incoherent — it would leave three other trackers firing pre-consent while implying the problem was solved. A consent banner plus a preference store that gates all four belongs in its own PR. Flagged to the team rather than fixed here. |
||
| </body> | ||
| </html> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| "use client"; | ||
|
|
||
| import Script from "next/script"; | ||
| import { usePathname, useSearchParams } from "next/navigation"; | ||
| import { Suspense, useEffect, useRef } from "react"; | ||
|
|
||
| /* X (Twitter) Ads universal website tag. `twq('config', <pixel id>)` loads the | ||
| tag and reports the initial page load; conversion events are then reported | ||
| with twq('event', 'tw-<pixel>-<event>', {...}) from wherever they happen. | ||
|
|
||
| uwt.js installs no history listener, so App Router navigations would | ||
| otherwise go unreported. Each route change re-calls twq('config', ...), | ||
| which sends a fresh page-load beacon every time it runs (the command is | ||
| 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 tag locally, temporarily drop the NODE_ENV guard. */ | ||
|
|
||
| const X_PIXEL_ID = "re2t6"; | ||
|
|
||
| declare global { | ||
| interface Window { | ||
| twq?: (...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.twq?.("config", X_PIXEL_ID); | ||
| }, [pathname, searchParams]); | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| export function XPixel() { | ||
| if (process.env.NODE_ENV !== "production") return null; | ||
|
|
||
| return ( | ||
| <> | ||
| <Script id="x-pixel" strategy="afterInteractive"> | ||
| {` | ||
| !function(e,t,n,s,u,a){e.twq||(s=e.twq=function(){s.exe?s.exe.apply(s,arguments):s.queue.push(arguments);},s.version='1.1',s.queue=[],u=t.createElement(n),u.async=!0,u.src='https://static.ads-twitter.com/uwt.js',a=t.getElementsByTagName(n)[0],a.parentNode.insertBefore(u,a))}(window,document,'script'); | ||
|
mikaalnaik marked this conversation as resolved.
|
||
| twq('config','${X_PIXEL_ID}'); | ||
| `} | ||
| </Script> | ||
| {/* useSearchParams requires a Suspense boundary in the App Router */} | ||
| <Suspense fallback={null}> | ||
| <PageViewTracker /> | ||
| </Suspense> | ||
| </> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.