On Next.js (App Router), load Madder's pixel snippet with next/script in app/layout.tsx, then call fbq('track', 'Lead') in the client after the form succeeds and the router pushes to the thank-you page. About 20 minutes for a developer.
Madder already created the Meta pixel in your connected ad account; step 2 of the tracking wizard prints the base code with your ID. Put the ID in NEXT_PUBLIC_META_PIXEL_ID rather than hard-coding it.
Before you start#
- Next.js 13.4 or later with the
app/directory (Pages Router: the sameScriptcomponent works inpages/_app.tsx). - A deploy you can push to, and the site's production domain.
1. Load the base code in the root layout#
Paths as of September 2026. In app/layout.tsx add an inline Script with the default afterInteractive strategy (it loads after hydration, which Next.js recommends for tag managers and analytics). Inline scripts must carry an id:
import Script from 'next/script'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
<Script id="meta-pixel" strategy="afterInteractive">
{`/* paste the body of Madder's snippet here (the !function... block,
fbq('init', '${process.env.NEXT_PUBLIC_META_PIXEL_ID}') and
fbq('track', 'PageView')), without the <script> tags */`}
</Script>
</html>
)
}
Drop the snippet's <noscript> image tag; it is optional. Next.js runs the script once per full page load, so PageView fires once per session. Fine for Madder, which needs Lead.
2. Fire Lead after the form succeeds#
Fire in the client component that owns the form, only after your server action or API call resolves, then navigate:
'use client'
import { useRouter } from 'next/navigation'
declare global { interface Window { fbq?: (...args: unknown[]) => void } }
export function LeadForm() {
const router = useRouter()
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const res = await fetch('/api/lead', { method: 'POST', body: new FormData(e.currentTarget) })
if (!res.ok) return
window.fbq?.('track', 'Lead')
router.push('/thank-you')
}
return <form onSubmit={onSubmit}>{/* fields */}</form>
}
Firing before router.push avoids the alternative trap: a useEffect on /thank-you runs on every visit, including reloads and bookmarks, and double-counts. If you must fire on the page instead, gate it with a one-time flag in sessionStorage.
3. The Google tag#
Use @next/third-parties/google (npm install @next/third-parties@latest): render <GoogleTagManager gtmId="GTM-…" /> in the root layout and send the conversion with sendGTMEvent({ event: 'lead' }) next to the fbq call, or render <GoogleAnalytics gaId="G-…" /> if you have no container. Which IDs and which conversion action: Google tag and conversion tracking.
4. Verify#
- Deploy to production (the ID comes from an env var; set it in your host's environment settings).
- Install the Meta Pixel Helper Chrome extension (now listed as Meta Ads Data Advisor). Open the live site: one pixel, status Active, your ID. Submit the form: Lead fires once.
- In Meta Events Manager: Data sources → your pixel → Test events, enter your URL, submit the form, confirm PageView and Lead.
- In Madder's wizard click I've installed it.
If it doesn't work#
fbq is not defined— the form fired beforeafterInteractivefinished loading; usewindow.fbq?.(...)and, for very fast submits, move theScripttobeforeInteractive.- Pixel missing in production only —
NEXT_PUBLIC_META_PIXEL_IDis unset on the host; rebuild after setting it (public env vars are inlined at build time). - Lead fires twice — React Strict Mode runs effects twice in development only; in production, check you did not fire both in the handler and in a
useEffecton the thank-you page. - CSP blocks the script — allow
https://connect.facebook.netinscript-srcand pass yournoncetoScript.