MadderHelp Request early access

Install Meta and Google tracking on a Next.js site

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.

Checked 6 Sep 2026nextjs

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 same Script component works in pages/_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#

  1. Deploy to production (the ID comes from an env var; set it in your host's environment settings).
  2. 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.
  3. In Meta Events Manager: Data sources → your pixel → Test events, enter your URL, submit the form, confirm PageView and Lead.
  4. In Madder's wizard click I've installed it.

If it doesn't work#

  • fbq is not defined — the form fired before afterInteractive finished loading; use window.fbq?.(...) and, for very fast submits, move the Script to beforeInteractive.
  • Pixel missing in production only — NEXT_PUBLIC_META_PIXEL_ID is 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 useEffect on the thank-you page.
  • CSP blocks the script — allow https://connect.facebook.net in script-src and pass your nonce to Script.