Next.js
Databuddy works with both the App Router and Pages Router. Add it once near the root of your app; page views and client-side route changes are tracked automatically.
Start with pageviews and one custom event, then use the same data for goals and funnels. The Free plan includes 10,000 events per month; error tracking starts on Hobby. See current plans and limits.
Try a runnable App Router example
Download the minimal Next.js example to test a root layout, client-side navigation, and one plan_selected event. Extract it outside this monorepo, run bun install --frozen-lockfile, and follow its README to configure a separate test website. Without a Client ID, the example keeps collection disabled and explains the missing setting.
The example does not create accounts or take payments. For confirmed backend outcomes, continue with the signup-to-paid implementation guide. For recorded test results and their limitations, inspect the route-event and delivery-recovery check.
Before you start
NEXT_PUBLIC_DATABUDDY_CLIENT_ID=your-client-idInstall
bun add @databuddy/sdkYou can also install with npm or yarn:
npm install @databuddy/sdk
yarn add @databuddy/sdkApp Router
Add <Databuddy /> to app/layout.tsx:
import { Databuddy } from "@databuddy/sdk/react";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Databuddy
clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
/>
{children}
</body>
</html>
);
}The SDK's React entry point includes the client boundary, so your root layout can stay a Server Component. Custom event handlers belong in a Client Component, as shown below.
Pages Router
Add <Databuddy /> to pages/_app.tsx:
import { Databuddy } from "@databuddy/sdk/react";
import type { AppProps } from "next/app";
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Databuddy
clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
/>
<Component {...pageProps} />
</>
);
}Databuddy sends the first page view when it loads and listens for route changes. Do not add your own router.events or usePathname screen-view tracking unless you intentionally disabled Databuddy's automatic route tracking.
Script Tag Setup
If you prefer not to use the React component, add the script in pages/_document.tsx:
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head>
<script
async
crossOrigin="anonymous"
data-client-id={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID}
src="https://cdn.databuddy.cc/databuddy.js"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}Use either the React component or the script tag, not both.
Optional errors and Web Vitals
Add trackWebVitals to the React component to collect performance measurements. Add trackErrors if your plan includes error tracking. Both are off by default, and the collected measurements count toward your event allowance.
<Databuddy
clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
trackWebVitals
trackErrors
/>For the script tag, the equivalent attributes are data-track-web-vitals and data-track-errors.
Track Custom Events
Use the SDK helper from client components:
"use client";
import { track } from "@databuddy/sdk";
export function SubscribeButton() {
return (
<button
onClick={() =>
track("subscribe_clicked", {
location: "header",
})
}
type="button"
>
Subscribe
</button>
);
}This records a click, not a completed subscription. Record outcomes such as account creation or payment after the backend confirms success, using the Node SDK. Send each completed outcome from one place so a browser call and server call do not double-count it.
Use stable event names and small properties such as the button location. Do not send emails, passwords, tokens, or entire form values.
Verify your first events
Verify on your deployed site. For a local test, the standard tracker skips localhost: temporarily add debug to <Databuddy /> to load the debug bundle, or use https://cdn.databuddy.cc/databuddy-debug.js in your script tag. Use a separate Databuddy website and Client ID for local test data, and remove debug mode before deploying.
Keep the delivery stages separate: a loaded script proves installation, an outgoing request proves an attempted send, and a successful collector response must be checked for rejection details. Confirm the event in the intended website's report before treating the journey as working. The browser track() helper returns void; updating your UI after calling it is not a delivery acknowledgement.
Use Back and Forward after the first soft navigation and check the paths again. For a recovery check, block the script in browser developer tools, reload, and confirm your app still works. Unblock it, reload, and repeat the journey. This verifies recovery on a new page load; it does not prove durable retry of events attempted while collection was blocked.
Databuddy DevTools can help inspect the tracker and custom events while you develop.
Production Setup
Add NEXT_PUBLIC_DATABUDDY_CLIENT_ID to your deployment provider before building. On Vercel, choose the deployment environments that should collect data. Next.js embeds public environment variables at build time, so rebuild and redeploy after changing the Client ID.
To avoid test data, disable tracking outside production:
<Databuddy
clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
disabled={process.env.NODE_ENV !== "production"}
/>NODE_ENV is also production for many preview deployments. If previews should stay out of production analytics, use your deployment provider's environment indicator for disabled, or give previews a separate Databuddy website and Client ID.
Serve Databuddy from your own domain
Ad blockers and privacy extensions often block requests to cdn.databuddy.cc and basket.databuddy.cc, so visitors who use them never reach your analytics. Two rewrites let your own domain serve the script and receive events, which keeps those visitors in your data.
Add the rewrites to next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async rewrites() {
return [
{ source: "/_p/s.js", destination: "https://cdn.databuddy.cc/databuddy.js" },
{ source: "/_p/e/:path*", destination: "https://basket.databuddy.cc/:path*" },
];
},
};
export default nextConfig;Then point <Databuddy /> at those paths:
<Databuddy
clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
scriptUrl="/_p/s.js"
apiUrl="/_p/e"
/>If you load Databuddy through the c15t consent manager, pass the same scriptUrl and apiUrl values to its databuddy() helper instead.
Keep these points in mind when you set it up:
To check the setup, open your deployed site with the Network tab open. The script should load from /_p/s.js and events should post to paths such as /_p/e/batch, with no script or event requests going to cdn.databuddy.cc or basket.databuddy.cc. Feature flag requests to api.databuddy.cc are expected. The tracker sends nothing from localhost, so test on a deployment rather than your dev server.
Replace an existing tracker
If you briefly run both providers, compare the same dates and definitions. Different session, consent, and bot-filtering rules can produce different totals; an exact count match is not a setup requirement.
Troubleshooting
No data in the dashboard
Pageviews work, but an outcome is missing
Confirm the event call runs after the actual outcome, uses the same website, and is not skipped by an error or redirect. If you send it from a serverless route handler, flush the Node SDK before the handler exits. Check the custom event helpers and server event setup.
Pageviews appear twice
Check for a second script in a tag manager, nested layout, or shared provider. If automatic route tracking is enabled, remove any manual pageview effect before repeating a direct load, soft navigation, and Back/Forward check. Keep custom events tied to user actions or confirmed outcomes rather than a component render.
If you are evaluating libraries before installation, read the React and Next.js SDK comparison. If the analytics service must run on infrastructure you manage, review the self-hosted platform comparison separately from the client-side integration.
Related
How is this guide?