To measure signup-to-paid conversion in a Next.js App Router application, record account creation, the first useful product action, and confirmed payment as separate events after each operation succeeds. Send those outcomes to the same Databuddy website with the same stable profile ID, then create an ordered funnel from their event names.
Pageviews tell you which pages someone visited. They do not establish that an account was created or a payment succeeded. This guide separates browsing activity, user intent, and completed business actions so a visit to a success page cannot quietly become your definition of revenue.
The downloadable example runs a synthetic signup, first-project, and payment-confirmation flow. It uses the published browser and Node SDKs with a local collector, without real accounts, charges, or API credentials. Its tests verify local event delivery and recovery, rather than ingestion into a Databuddy dashboard. Download the runnable example.
Define the three outcomes before writing tracking code
For this example, activation means the first successful project creation. Your product may have a different first useful action; choose one and write down the rule.
| Event | Record it after | Do not substitute |
|---|---|---|
signup_completed | The application creates the account successfully. | A signup button click or invalid submission. |
activation_completed | The account creates its first project successfully. | Opening the project editor or creating every subsequent project. |
payment_completed | The account's first successful payment is confirmed. | Opening checkout, starting a trial, or visiting a success URL. |
Each event uses the same website and opaque profileId. In production, derive that ID from authenticated application state or a trusted payment-to-account mapping. Do not accept a browser-supplied profile ID as proof of who performed an operation, and do not use an email address as the identifier.
The current Databuddy funnel implementation uses a 24-hour progression window. The reporting date range is a separate setting. This guide demonstrates a short conversion journey; a payment after a two-week trial can exist in Events while remaining outside this funnel's completion window.
Run the Next.js example
The example is a standalone App Router application, with Next.js 16.4.2, React 19.3.1, and @databuddy/sdk 3.3.0 verified on October 28, 2026. Install with Bun using the included lockfile:
bun install --frozen-lockfile
cp .env.example .env.local
bun run dev
Open http://127.0.0.1:4317. If that port is occupied, run bunx next dev --hostname 127.0.0.1 --port 45287 instead.
Complete synthetic signup, then the first project, then the synthetic payment. The page shows saved business steps separately from analytics delivery. Inspect the local capture ledger to see the three outcome names and their shared profile ID.
The fixture uses an application session cookie and in-memory state. It deliberately refuses collection in production mode. It is an executable explanation of event placement and failure handling, rather than authentication, billing, or durable storage infrastructure to deploy.
Install the browser tracker once
For your application, install the SDK and mount its component once in the root layout:
bun add @databuddy/sdk
// app/layout.tsx
import { Databuddy } from "@databuddy/sdk/react";
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>
{children}
<Databuddy clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!} />
</body>
</html>
);
}
The public Client ID identifies the website receiving browser events. The SDK component supplies its own client boundary, so the root layout can remain a Server Component. Follow the Next.js integration reference for setup options and local debugging.
Automatic tracking handles App Router navigation. Avoid adding a second manual pageview effect simply because the URL changes. After setup, verify a direct page load and a client-side navigation separately.
Keep the API key on the server. Next.js embeds NEXT_PUBLIC_ variables in browser bundles at build time, so changing the public website ID requires rebuilding. Next.js documents that environment-variable behavior.
Send completed outcomes from the server
Use the Node entry point for server events. The browser track() helper is not a server collection API.
The following helper only delivers an event. Its caller owns authentication, business state, transition uniqueness, and durable retries:
// lib/analytics.ts
import "server-only";
import { Databuddy } from "@databuddy/sdk/node";
type Outcome =
| "signup_completed"
| "activation_completed"
| "payment_completed";
export async function recordOutcome(profileId: string, name: Outcome) {
const apiKey = process.env.DATABUDDY_API_KEY;
const websiteId = process.env.DATABUDDY_WEBSITE_ID;
if (!apiKey || !websiteId) {
throw new Error("Server analytics configuration is missing");
}
const analytics = new Databuddy({
apiKey,
websiteId,
source: "nextjs-app",
});
const queued = await analytics.track({
name,
profileId,
eventId: `${profileId}:${name}`,
});
if (!queued.success) return queued;
return analytics.flush();
}
Set the server websiteId to the website represented by the browser Client ID. The API key needs permission to track events. Store it in server environment configuration, without the NEXT_PUBLIC_ prefix. See the Node SDK reference.
A successful track() response can mean the event was queued. Await flush() before the request exits, and inspect its returned success and delivery values. A promise resolving is not, by itself, evidence of delivery. This request-scoped helper keeps the sample simple; assess batching and request latency for your own workload.
The event ID above represents a once-per-account transition. It is unsuitable for every purchase or renewal, where each operation needs its own stable identifier. An SDK deduplication cache does not provide permanent, cross-process exactly-once delivery.
Keep business success separate from analytics success
Call the helper after the application has committed the corresponding transition. If signup succeeds and analytics fails, the person still has an account. Asking them to repeat signup to repair tracking creates a second problem.
The runnable fixture makes this separation visible:
- Complete synthetic signup.
- Select Reject next analytics event.
- Complete the first project.
- Observe that the business stage advances to 2 while
activation_completeddelivery is markedfailed. - Select Retry analytics only. Delivery changes to
delivered, while the saved business stage stays at 2.
The fixture remembers state only in its local process. In production, use your application's durable event or job mechanism to preserve delivery work after a committed transition and retry it independently. Do not mistake an in-memory SDK queue for a transactional outbox.
The same principle applies to payments. Confirm a real payment through your provider's verified server flow, map it to the application account, and record only the first transition to paid for this funnel. Payment callbacks can repeat or arrive asynchronously. The fixture's synthetic confirmation button does not implement a payment-provider webhook.
Use browser events for intent and context
A browser intent event can help explain the steps leading to a completed outcome:
"use client";
import { identify, track } from "@databuddy/sdk";
// After the application supplies the authenticated account's opaque ID:
identify(accountId);
// In the button's interaction handler:
track("signup_clicked", { location: "pricing" });
These snippets belong in the appropriate authenticated-state and interaction handlers. They are not a complete component or a reason to record conversions in a mount effect.
signup_clicked is intentionally distinct from signup_completed. Browser track() returns void, so it does not acknowledge ingestion. A blocked browser script can leave you with server outcomes but incomplete browsing context.
The three server outcomes can share identity through the same nonempty profile ID. Connecting anonymous acquisition activity to that profile is a separate identity-linking task. Do not assume that adding a user ID to later server events retroactively proves a browser-to-payment attribution chain. Review the tracker identity helpers for your application, and clear the browser profile when the account signs out.
Create and verify the ordered funnel
On a separate test website, first verify that all three custom events appear. Then create an ordered funnel with these targets:
signup_completed → activation_completed → payment_completed
Use a synthetic account that completes all three steps within 24 hours and another that stops after signup. The first should progress through all stages; the second should remain a dropout after the first stage. Verify those dashboard results independently from local collector acceptance.
The sample's browser suite checks these implementation behaviors:
| Check | Verified local observation |
|---|---|
| Complete all three synthetic outcomes. | Three server events arrive with one profile ID. |
| Follow the sample plans link. | Automatic client-side route tracking records the navigation. |
| Request payment before signup. | The application rejects it without a completed outcome. |
| Repeat synthetic payment confirmation. | The fixture does not create another first-payment event. |
| Reject activation collection, then retry. | Business state survives; analytics delivery recovers without repeating the transition. |
Run the checks yourself with bunx playwright install chromium, followed by bun run test. Tests download the published tracker script, record its hash, and route its requests to the local collector. They do not send the synthetic events to a production website.
Interpret missing conversions carefully
If an expected outcome is absent, check the application transition, event name, website ID, profile ID, and delivery result before drawing a product conclusion. If all events exist but the funnel does not complete, check order, reporting dates, and the 24-hour progression window.
Do not infer that every dropout reflects poor onboarding. Collection failures, identity mismatches, a blocked browser tracker, and an upgrade outside the window can each change what the report shows.
Start with the Next.js integration guide, then verify one completed action before expanding the funnel. If you are still choosing an SDK, the React and Next.js analytics comparison addresses that earlier decision.
Inspect a recorded Next.js integration check
The Next.js route-event and delivery-recovery check includes pinned source, per-run results, and unsuccessful setup batches. It verifies a disclosed configuration against a synthetic local collector and explains why that evidence does not establish hosted reporting performance.