next-facebook-pixel
v1.0.0
Published
All-in-one, framework-aware Facebook Pixel for Next.js. Typesafe events, SPA route-change PageView tracking, auto loaded via layout component.
Maintainers
Readme
Next.js Facebook Pixel — all-in-one typesafe solution
Published as
next-facebook-pixel. Zero runtime dependencies. Works with App Router and Pages Router.
Install
npm install next-facebook-pixelimport { Pixel, PixelProvider } from "next-facebook-pixel";Prefer to vendor it instead of installing? Copy the src/ folder into your app
(e.g. lib/facebook-pixel/) and import from there — no build step needed.
1. Connect it on the layout
App Router (app/layout.tsx)
import { PixelProvider } from "next-facebook-pixel";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<PixelProvider pixelId={process.env.NEXT_PUBLIC_FB_PIXEL_ID}>
{children}
</PixelProvider>
</body>
</html>
);
}Pages Router (pages/_app.tsx)
import { PixelProviderPages } from "next-facebook-pixel";
export default function App({ Component, pageProps }) {
return (
<PixelProviderPages pixelId={process.env.NEXT_PUBLIC_FB_PIXEL_ID}>
<Component {...pageProps} />
</PixelProviderPages>
);
}Props
| Prop | Default | Meaning |
| --- | --- | --- |
| pixelId | — | FB Pixel ID (16-digit). Pixels are skipped if falsy |
| trackPageViews | true | Fire PageView on every client-side route change |
| disabled | false | Fully mute the pixel (e.g. until cookie consent) |
| enableInDev | false | By default no events fire in next dev so you don't pollute your data |
| userData | — | Advanced Matching user data (email, phone, etc.) |
| autoConfig | true | Meta's automatic config (server-side events sync) |
Requires NEXT_PUBLIC_FB_PIXEL_ID in .env.local:
NEXT_PUBLIC_FB_PIXEL_ID=1234567890123452. Track events anywhere — one line each
import { Pixel } from "next-facebook-pixel";
// Standard events — auto-routed to fbq('track', ...)
Pixel.addEvent("PageView");
Pixel.addEvent("ViewContent", { content_ids: ["sku-1"], value: 29.99, currency: "USD" });
Pixel.addEvent("Purchase", { value: 89.97, currency: "USD", contents: [{ id: "sku-1", quantity: 3 }] });
Pixel.addEvent("Lead", 250); // a bare number/string becomes { value: ... }
// Custom events — auto-routed to fbq('trackCustom', ...)
Pixel.addEvent("NinthLevelHealth", { level: 4, xp: 1200 });
Pixel.addEvent("SubscribeToNewsletter", "newsletter");
// Type-safe shortcuts — these are typed checks (e.g. purchase forces value + currency)
Pixel.purchase({ value: 89.97, currency: "USD" });
Pixel.viewContent({ content_ids: ["sku-1"] });
Pixel.search({ search_string: "blue dress" });
Pixel.subscribe({ value: 10, currency: "USD" });Pixel.addEvent(name, data, eventId?) takes three forms of data:
| You pass | Becomes |
| --- | --- |
| an object { value, currency, ... } | sent as-is |
| a number/string 250 / "abc" | { value: 250 } |
| nothing | no data argument |
Type safety
- Typed event names via
Pixel.EVENTS—Pixel.EVENTS.Purchase(typo-proof). - Each of the 18 standard events has a typed params interface
(
PurchaseData,ViewContentData, …). Passing wrong/missing fields is a compile error. Purchase,Donate, andSubscriberequirevalue+currency.- Custom events accept any object, or a plain value.
Other class API
Pixel.disableAutoConfig();
Pixel.setEndpoint("https://api.facebook.com/tracking"); // alt endpoint
Pixel.setUserParams({ em: "hashed-email" }); // update Advanced Matching
Pixel.grantConsent(); // fbq('consent','grant')
Pixel.revokeConsent(); // fbq('consent','revoke')
Pixel.setEnabled(false); // mute everything
Pixel.EVENTS; // all standard namesHow SPA route changes are handled (the important part)
Next.js is a single-page app: navigating / → /shop → /checkout doesn't
reload the browser, so Meta's stock snippet fires PageView only once for
the whole session.
This solution tracks every client-side route change:
- App Router:
<PixelProvider>watchesusePathname()+useSearchParams()and firesPixel.pageView()on every navigation (including query-only changes like/shop?page=2). - Pages Router:
<PixelProviderPages>listens torouter.events.routeChangeComplete+hashChangeComplete.
Pixel.pageView() dedupes by full URL: the same page can never fire PageView
twice, so double-firing (React StrictMode, fast back/forward, link to same page)
is impossible.
Manual tracking still works during navigation —
Pixel.addEvent(…)pushes straight into the realfbqqueue (window.fbq), which buffers events untilfbevents.jsloads.
Cookie consent (GDPR)
<PixelProvider
pixelId={process.env.NEXT_PUBLIC_FB_PIXEL_ID}
disabled={!hasConsent} // flip this from your consent banner
/>Pixel.grantConsent(); // after the user accepts — re-enables + sends consent
Pixel.revokeConsent(); // on opt-outWhat's inside src/
| File | Purpose |
| --- | --- |
| pixel.ts | Pixel class — init/base code, addEvent, typed standard-event shortcuts, consent, endpoint, buffering, URL dedupe |
| PixelProvider.tsx | App Router layout component (loads base code + auto SPA PageView tracking) |
| PixelProviderPages.tsx | Pages Router equivalent |
| types.ts | All types: StandardEvent, PurchaseData, PixelEventData, UserData, … |
| index.ts | Re-exports |
Standard events covered: PageView, ViewContent, Search, AddToCart,
AddToWishlist, AddPaymentInfo, InitiateCheckout, Purchase, Lead,
CompleteRegistration, Contact, CustomizeProduct, Donate,
FindLocation, Schedule, StartTrial, SubmitApplication, Subscribe.
Verify & publish
npm run typecheck # strict tsc over src/
npm run build # tsup -> dist/index.js (ESM, "use client" banner) + dist/index.d.ts
npm run smoke # runtime tests against a mocked fbq (dedupe, buffering, coercion)
npm pack --dry-run # preview the exact tarball that would be published
npm publish # requires: npm login + npm accountprepublishOnly runs typecheck && build automatically, so you can never
publish a stale or broken build.
