Skip to content

DataFast goals versus page views, and when you need neither

Page views are free and automatic. Goals cost events and need a name, a reason and a side of the network. A decision table, the naming rule, and what never to put in a goal.

DataFast3 min readships at docs/solutions/datafast/goals-vs-pageviews.md

Tags: datafast · analytics · goals · pageviews · naming · nextjs

A new DataFast install tends to go one of two ways. Nobody adds a goal, so the dashboard can say where visitors came from but not what they did. Or someone adds forty, and the monthly event quota is gone by the 12th.

The fix is knowing what each one is for.

Page views: already done

The script records a page view on load, and on every client-side navigation: it listens to history.pushState and popstate. That covers Next.js App Router links. You do not need a usePathname effect, and adding one double counts.

So these need no code at all:

  • "How many people see the pricing page?" Filter by page.
  • "Where do visitors who read the docs come from?" Filter by page, then look at referrers.
  • "Which landing page converts?" Revenue by entry page. That is the point of the product.

Goals: things a URL cannot tell you

A goal is an action on a page, or an outcome on the server:

QuestionPage view or goal
Did they open pricing?Page view
Did they click "Start trial" on pricing?Goal, browser: checkout_started
Did they create an account?Goal, server: signup_completed
Did they finish onboarding step 3?Goal, server: onboarding_step_completed with step: 3
Did they pay?Neither. DataFast reads payments from your provider

That last row matters. Revenue comes from the connected payment provider, with attribution from checkout metadata. A purchase_completed goal with an amount is a second, worse copy.

Name goals like records

object_verb, past tense, snake_case:

checkout_started
signup_completed
project_created
newsletter_subscribed

DataFast accepts lowercase letters, digits, _, - and :, up to 64 characters. That is permission, not a convention. Pick one and enforce it, or in a year you have signup, sign-up and signup:done as three series.

Never put a value in the name. plan_pro_bought and plan_team_bought are one goal with a plan property. Otherwise every new plan breaks every funnel.

Stay away from DataFast's reserved names: payment, free_trial, trial_started, trial_converted, the subscription_* family and identify. It sends those itself once a payment provider is connected.

Make the catalogue a type

export interface GoalCatalogue {
  checkout_started: { plan: string; interval: "month" | "year" };
  signup_completed: { method: "email" | "oauth" };
}

export function trackGoal<N extends keyof GoalCatalogue>(
  name: N,
  properties: GoalCatalogue[N],
): void {
  window.datafast?.(name, toGoalMetadata(properties));
}

Now trackGoal("signup") does not compile. The second spelling of a goal is a type error in the editor, not a mystery series in the dashboard.

Properties: 10 strings, no people

DataFast keeps at most 10 parameters per goal, keys in lowercase, values up to 255 characters, all stored as strings. Use them for things you will filter by: plan, source, step, method.

DataFast's own examples pass email and name as goal parameters. Do not. Goal parameters show up for everyone with dashboard access, forever. If you need to find a person, use identify with your database user id, and keep the email in your database.

Where to fire it

  • Browser for intent: clicks, forms opened, a screen seen. Some of it will be lost to blockers and closed tabs, and that is fine for intent.
  • Server for facts: an account row, a saved step. Use the Goals API after the write. It is complete, and a user cannot fake it from DevTools.

Never both for the same name. Two copies of one event double the count, and nothing can tell them apart later.

A budget, not a wishlist

Every goal is billed as an event. Before adding one, write the question it answers in one sentence. "Which channels bring people who finish onboarding?" earns a goal. "Might be useful later" does not.