Skip to content

Analytics

Next.js boilerplate with DataFast

Web analytics that tells you which channel made money, not just which one made visits.

Revenue-first web analytics. The DataFast script loads from your own origin after hydration. A route handler forwards events, so ad blockers have no hostname to match. Goals are typed against a catalogue and stripped of personal data. Server-side goals go through the Goals API after the response. With Stripe, Polar or Dodo selected, every checkout carries the visitor id in its metadata. Revenue lands on the channel that earned it.

What DataFast adds to the agent layer: 3 rules · 1 skill · 6 solution docs · 1 MCP server

Maintained by @raviMITNext.js on Vercel

From the manifest

Should you pick DataFast?

Pick it if

Indie hackers and small SaaS teams who want one answer: which referrer, campaign or page produced revenue. Checkout attribution is wired for Stripe, Polar and Dodo. One script tag, no event plan needed on day one.

Watch out for

  • It is web analytics, not product analytics. Page views, referrers, goals and revenue. No session replay, no feature flags, no cohorts over custom person properties. Pick PostHog if you need those.
  • Attribution depends on one cookie. The checkout must carry the datafast_visitor_id from the same browser that visited, which is what the checkout-attribution slot does. Payments made on another device, or before consent, land as direct traffic.
Show 3 more
  • Goal parameters are capped at 10 per event and 255 characters per value, all stored as strings. Typed properties are converted for you; numbers you want to sum belong in your database.
  • Every goal counts toward your monthly event quota. Track decisions, not every click.
  • The script writes a first-party cookie for a year. In the EU and the UK that needs consent. src/lib/analytics/consent.ts ships a gate you must switch on before you serve traffic there.

What it costs

No free plan. A 14-day trial with no card. Starter is $9/month (1 site, 1 team member) and Growth $19/month (30 sites, 30 team members), both at 10k monthly events. Go over and tracking continues, but you must upgrade to open the dashboard. API, CLI and MCP access are on both plans.

Prices change. Check with DataFast before you commit.

registry/tested.yaml

Tested with DataFast

Each pair was installed, typechecked, linted, built and booted together.

Database
NeonSupabase
Admin panel
Admin panel
Error tracking
Sentry
Customer support
Crisp

What it adds

What DataFast adds to the repo

Read straight from the datafast manifest, so it is exactly what lands in your repo.

Environment variables

  • NEXT_PUBLIC_DATAFAST_WEBSITE_IDRequiredPublic, reaches the browser

    The website id from DataFast. Public by design: it sits in every page's HTML as data-website-id. Without it the script is never rendered and the build still succeeds.

    Where to get it
    DataFast -> Website settings -> General (starts with dfid_)
    Placeholder
    dfid_exampledonotuse00000
  • NEXT_PUBLIC_DATAFAST_DOMAINOptionalPublic, reaches the browser

    The root domain you registered in DataFast, used for the cookie scope. Defaults to the host of NEXT_PUBLIC_APP_URL without "www.". Set it when your app runs on a subdomain such as app.example.com.

    Where to get it
    DataFast -> Website settings -> General (the domain you added)
    Placeholder
    example.com
  • NEXT_PUBLIC_DATAFAST_ALLOW_LOCALHOSTOptionalPublic, reaches the browser

    "true" sends events from localhost. Off by default, because the script ignores localhost on purpose and you do not want your dev clicks in the production numbers. Turn it on for an afternoon of debugging, then off.

    Placeholder
    false
  • DATAFAST_API_KEYOptional

    Website API key, server only. Needed for server-side goals (trackGoalServer) and for the datafast:api-key verify check. Not needed for page views, browser goals or revenue attribution.

    Where to get it
    DataFast -> Website settings -> API -> Create API key
    Placeholder
    df_exampledonotuse0000000000000000

Dependencies

  • server-only^0.0.1

MCP server

  • datafast

    URL
    https://datafa.st/api/mcp

Files it writes

10 files, at these exact paths.

  • src/8 files
    • app/2 files
      • api/1 file
        • events/1 file
          • route.ts
      • js/1 file
        • script.js/1 file
          • route.ts
    • lib/6 files
      • analytics/6 files
        • attribution.ts
        • consent.ts
        • datafast-client.ts
        • datafast-server.ts
        • goals.ts
        • provider.tsx
  • variants/2 files
    • with-clerk/1 file
      • slots/1 file
        • auth-public-routes.ts
    • with-payments/1 file
      • slots/1 file
        • checkout-attribution.ts

Stack slots it fills

The stack declares these injection points; this battery supplies the fragment, so the provider tree, the env check and the schema stay one file each instead of many.

  • @slot env-required
  • @slot legal-processors
  • @slot providers
  • @slot verify-checks

The differentiator

What DataFast teaches your agent

Other starter kits stop at the package. This is the part an agent reads: where it may work, what it must never do there, and the problems someone already solved.

Rules (3)

Loaded when the agent opens a matching file.

Goals come from the catalogue, are named object_verb, and never carry personal data

Loads onsrc/lib/analytics/**src/app/**src/components/**.claude/rules/goals.md
One place a goal is born

src/lib/analytics/goals.ts declares every goal this repo sends. A goal not in GoalCatalogue does not compile. That is the point.

  • Never call window.datafast() directly. Use trackGoal() from @/lib/analytics/datafast-client in the browser and trackGoalServer() from @/lib/analytics/datafast-server on the server.
  • Never add data-fast-goal attributes. They skip the catalogue, the type check and the PII filter.
  • A new goal means three edits in goals.ts: the name, its property type, and its line in GOAL_QUESTIONS. /add-goal does all three.
  • Page views are not goals. The script records them, App Router navigations included. Never fire a page view by hand.
Naming

object_verb, past tense, snake_case, at most 64 characters. Checked by GOAL_NAME_PATTERN.

Write thisNot thisWhy
checkout_startedstartCheckoutA goal records something that happened
signup_completedsignup-completeOne casing, past tense
project_createdcreate_projectObject first, so the list groups by object
onboarding_step_completedonboarding_2_doneThe step is a property, not part of the name
  • Never use a name in RESERVED_GOAL_NAMES: payment, subscription_started, trial_started, identify and the rest. DataFast sends those itself once a payment provider is connected. A custom payment goal pollutes revenue.
  • Never encode a value in a name. plan_pro_bought and plan_team_bought are one goal with a plan property.
Properties
  • Low-cardinality labels you will filter by: plan, source, step, method.
  • At most 10 per goal, values at most 255 characters, all sent as strings. toGoalMetadata() enforces this and drops the rest.
  • No personal data. DataFast's own docs pass email and name as goal parameters. Do not copy that. FORBIDDEN_PROPERTY_KEYS strips them; do not rename email to contact to get past it.
  • No free text a user typed, no ids from another company's system, no secrets.
  • identifyVisitor() takes your database user id. Never the email.
Which side fires it
  • Browser (trackGoal): intent and interface. checkout_started, signup_started, a button a user clicked.
  • Server (trackGoalServer): facts after a write. signup_completed, onboarding_step_completed, newsletter_subscribed. Needs DATAFAST_API_KEY.
  • Never both for the same name. That doubles every count.
  • Money is neither. Payments reach DataFast from the payment provider. See the revenue attribution rule.

Every checkout carries the DataFast visitor id, and revenue is never a goal

Loads onsrc/lib/analytics/**src/lib/billing/**.claude/rules/revenue-attribution.md

DataFast attributes a payment to a channel by reading datafast_visitor_id from the checkout's metadata. No metadata, no attribution: the sale shows up as direct traffic and your best channel looks like your worst.

  • Every checkout, subscription or one-off, is created with its params passed through withCheckoutAttribution() from @/lib/analytics/attribution. The payment batteries already do this through the checkout-attribution slot. A new checkout path you add by hand must do it too.
  • Read the ids on the server, from the request cookies, inside the server action or route handler that creates the checkout. Never accept a visitor id from a request body: it is a claim, and it would let anyone attribute revenue to any channel.
  • Never add revenue as a custom goal. No payment goal, no purchase_completed with an amount. DataFast reads payments from the connected provider and de-duplicates them. A goal copy double counts.
  • withCheckoutAttribution() only adds keys. It never replaces userId or any other metadata the webhook relies on.
  • Attribution respects consent. In "required" mode it adds nothing until the visitor agreed. Do not bypass that by reading the cookie yourself.
  • The payment provider must also be connected in the DataFast dashboard. That is a one-time manual step, not code. If revenue shows as zero, check that first.

The DataFast script never blocks rendering, never skips consent, and never leaves your origin

Loads onsrc/lib/analytics/**src/app/layout.tsxsrc/app/js/**src/app/api/events/**.claude/rules/script-loading.md
Loading
  • The script is rendered once, by AnalyticsProvider in src/lib/analytics/provider.tsx, with next/script and strategy="afterInteractive". Never beforeInteractive, never a raw <script> in <head>, never a second copy in a page.
  • The provider renders the script beside the app, not around it. Nothing in the tree may wait for analytics. No await, no loading state, no Suspense boundary that depends on window.datafast.
  • A missing NEXT_PUBLIC_DATAFAST_WEBSITE_ID means "render nothing". It must never throw, and next build must pass with no env vars set.
  • Calls before the script loads are queued by installQueue(). Do not add a second queue snippet.
Proxy
  • src is /js/script.js and data-api-url is /api/events. Both are route handlers in this repo that forward to datafa.st. The browser never talks to datafa.st directly. Do not "simplify" src back to https://datafa.st/js/script.js: blocklists match that hostname.
  • src/app/api/events/route.ts forwards the visitor's IP, user agent, origin and referer. Remove those and every visitor is geolocated to your server.
  • Both routes answer inert on upstream failure: an empty script, a 204. A failing analytics call must never put a red error in a visitor's console.
  • If a route path collides with one of yours, rename the folder and update the matching constant in datafast-client.ts. Nothing else references it.
  • src/lib/analytics/consent.ts owns the policy. CONSENT_MODE is the only switch.
  • In "required" mode the script is not rendered, goals are not queued and checkouts carry no visitor id until grantAnalyticsConsent() runs. Every new code path that sends data to DataFast checks hasAnalyticsConsent().
  • Proxying changes the hostname, not the law. It is never a way around consent.

Skills (1)

Invoked by name.

  • /add-goal

    Add a DataFast goal to the typed catalogue and fire it from the right side of the network.

    .claude/skills/add-goal/SKILL.md

Solution docs (6)

Written before you hit the problem. Each one ships in the repo at docs/solutions/ and is published here as a cookbook page.

Show all 6

How it fits

What DataFast needs, and what it goes well with

The resolver enforces this before it generates anything, and names every addition it makes.

Requires

Nothing. DataFast stands on its own.

Pairs well with

  • A payments battery. Suggested, never added for you.

Compared with the alternatives

Build a repo with DataFast

Free and MIT. The builder opens with DataFast picked. You download the zip right away, and we email you the link too.