Meta-Recipe

Tracking Who's Visiting Your Site

Result:

Every pageview on your site lands in your tracking tool within minutes of a real deploy, and the tracking script itself is absent from any build that hasn't been handed a live key, so dev and untested previews never inflate your real numbers.

Show page metadata
Target question
How do I track who's visiting my site, so I have the real traffic volume that attribution models, growth dashboards, and other data work depend on?
Answer summary
Pick a web analytics tool, PostHog and Google Analytics are the two most common starting points, and load its tracking script from your site's shared layout. Gate that script behind a build-time environment variable so it's fully absent from local development and any build that hasn't been given a real key, then set that key in your hosting provider's dashboard, never in your repository. If your code isn't on GitHub yet, that comes first: create a repository, push your project to it, then connect it to your host so pushes trigger real deploys. Confirm a real pageview lands in your tool's dashboard before trusting any number downstream of it.
Authority level
primary
Audience
practitioner, developer, bot
Topics
PostHog, Google Analytics, build-time environment variables

The same fields sitting in this page's target-question, answer-summary,authority-level, and audience meta tags and its JSON-LD — bot-facing metadata, made human-visible.

Problem

Any system that reasons about your site, an attribution model, a growth dashboard, even a simple check on whether a change helped, needs one input underneath it: real traffic data. How many people showed up, when, and from where. Without it, every downstream claim about growth or performance is an assertion, not a result.

Traffic volume is also one of the first signals marketers check when judging a site’s health: whether a given day had real visitors, whether a change in copy or design moved anything at all. Before any of that can be measured, something has to actually be recording it.

PostHog is one way to do that: a single script tracks pageviews, clicks, and session data, with a free tier generous enough for most personal or early-stage sites. Google Analytics is another, and probably the most widely used option overall; a separate recipe will cover setting that up. Either way, the setup problem is the same: you want tracking running on your real, deployed site, but not in local development, not in every throwaway preview build, and not in a way that assumes a server you may not have.

Pattern

This is the general shape, whichever tool you pick. The specifics below follow PostHog, since that’s the one actually wired up on this site.

  1. Create an account with your tracking tool. Sign up for PostHog’s free tier (1M events/month), or Google Analytics, and create a project or property scoped to your domain.
  2. Copy your project’s key. PostHog calls it a “Project API key,” under Settings, then “Project API key.” Google Analytics calls its equivalent a “Measurement ID.” Hold onto it; it doesn’t go in your code yet.
  3. Install the tracking library. For PostHog: npm install posthog-js. Most tools ship a small package like this one.
  4. Initialize it from your site’s shared layout, gated behind an environment variable. In whatever file renders your <head> on every page, add the tool’s init call, but only run it when a specific environment variable is actually set. That keeps the script out of local development and out of any build that hasn’t been handed a real key.
  5. Name that variable something a reader of your code will understand, not a name borrowed from someone else’s project. The convention depends on your framework: Astro and Vite use a PUBLIC_ prefix (for example, PUBLIC_ANALYTICS_KEY), Next.js uses NEXT_PUBLIC_ (for example, NEXT_PUBLIC_ANALYTICS_KEY), and a plain Node server can use whatever you’d like, since nothing there is automatically exposed to the browser. Pick one name and use it consistently, in your code and in your host’s dashboard.
  6. If your project isn’t on GitHub yet, start there. Create a free account at github.com, then click “New repository” and give it a name. From your project’s folder on your computer, run git init, git add ., and git commit -m "Initial commit". GitHub’s own new-repository page shows the exact two commands to run next to connect and push it: one to add the repo as a remote, one to push your code up.
  7. Connect that GitHub repository to your hosting provider. In Cloudflare’s dashboard (Vercel’s and Netlify’s work the same way, with different screens), find the option to import or connect a Git repository, authorize access to your GitHub account, and select the repo. This is what makes a future git push trigger a real deploy.
  8. Set your environment variable in the host’s dashboard, not in your code. Find your host’s build-time environment variables screen. On Cloudflare, that’s nested inside the Worker’s Settings, under Build, a different panel from the top-level “Variables and secrets” section, which only applies at request time. Add the name you chose in step 5, and paste in the key from step 2 as the value.
  9. Trigger a fresh build. Push a commit, or use your host’s “retry build” or “redeploy” option. A build that already ran before you saved the variable won’t pick it up after the fact; it needs a new one.
  10. Confirm a real event lands before trusting anything downstream of it. Visit your live site, then check your tracking tool’s dashboard (PostHog calls this the “Activity” view) for a real pageview. Only once you see one is the pipeline actually working.
Step by step, screenshots included, with the debugging detour

The pattern above is the idealized shape of it. Here’s what actually happened wiring exitvelocities.com up to a real PostHog project, including two wrong turns.

1. Create the PostHog project

PostHog’s Quickstart screen after creating an “Exit Velocities” project. Every tool (“Product analytics,” “Session replay,” “Feature flags”) still reads “Not collecting data yet,” and the project token is right there to copy.

PostHog's Quickstart screen for a newly created 'Exit Velocities' project, showing the project token field and three tool cards all reading 'Not collecting data yet'
A fresh PostHog project, zero events, token ready to copy.

2. Find the build-time variables panel, not the runtime one

Cloudflare’s Worker Settings page has a “Variables and secrets” panel right at the top, but for a static-assets Worker like this one, it’s disabled (“Variables cannot be added to a Worker that only has static assets”), because that panel is for runtime variables a Worker script reads per-request. The one that actually matters is further down, inside the Build card, since your tracking key needs to exist while astro build runs, not at request time.

Cloudflare's Worker Settings page, Build section, showing a Git repository connection and a 'Variables and secrets' row reading 'None' with an Add button
The Build card’s own “Variables and secrets” row, separate from the disabled runtime panel above it.

3. Enter the key

The variable name chosen in the Pattern above, and the project token from step 1, as the value.

Cloudflare's 'Build environment variable' side panel with a variable name entered and a redacted value field, plus an Encrypt button
Name and value entered. The value is redacted here, though PostHog’s client token is meant to be public by design.

4. Saved, and the first wrong turn

Clicking Encrypt before saving turns it into a Secret-type variable, masked afterward. The instinct at the time was that Secret-type build variables might only be injected into the Worker’s runtime, not exposed to the npm run build step. That was plausible, but wrong. It works the same as a plaintext variable once a build actually picks it up.

Cloudflare's Build variables list showing the tracking key saved as Type 'Secret' with the value masked as 'Value encrypted'
Saved as a Secret, which turned out not to be the problem it looked like.

5. A fresh build actually picks it up

Saving a new build variable doesn’t retroactively apply to a build that already ran. Cloudflare’s Retry build triggers a genuinely new one. This log shows the variable listed under Build variables, with every stage (Initializing, Cloning, Installing, Building, Deploying) completing green in well under a minute.

Cloudflare build detail page showing Build #2c99ab96 with all five stages (Initializing, Cloning, Installing, Building, Deploying) marked complete with green checkmarks, and the tracking variable listed under Build variables
A retried build, the tracking variable declared, every stage green.

6. Confirm the domain isn’t routed somewhere else

The second wrong turn: suspecting exitvelocities.com might be bound to a separate “preview” environment that the branch builds weren’t reaching. The Worker’s Domains tab settled it: exitvelocities.com is a Production-environment route on this same Worker, and the deploy command is a bare npx wrangler deploy with no --env flag, so every successful build, from any branch, goes live on the real domain immediately. Nothing was silently absorbing the deploys.

Cloudflare's Domains tab for the exit-velocities Worker, showing exitvelocities.com listed under Custom Domains and Routes with Environment set to Production
exitvelocities.com, bound directly to this Worker’s Production environment.

7. Real events, landing

PostHog’s Activity view, showing real Pageview, Pageleave, and Web vitals events plus autocaptured clicks (including ones from testing the site’s own “Behind the build” panel), timestamped minutes after the retried build deployed.

PostHog's Activity view listing a stream of real Pageview, Pageleave, and Web vitals events plus autocaptured clicks, all from exitvelocities.com URLs, timestamped minutes apart
The pipeline, confirmed working end to end on the real domain.

Real result

Follow this pattern and your own traffic starts arriving in your tracking tool within minutes of a real deploy: every pageview and click, with nothing left to configure beyond the one build-time variable. That’s not a claim taken on faith. After wiring the tracking key into Cloudflare’s build-time variables and triggering a fresh build, PostHog’s Activity view started showing real Pageview, Pageleave, and Web vitals events, plus autocaptured clicks, from exitvelocities.com. The pipeline in the Pattern above works end to end on the real domain, not just locally.

Getting there took two wrong turns worth recording, since both looked plausible in the moment. First, that Cloudflare’s edge cache was serving a stale copy (it wasn’t; a hard refresh and a hand-copied page source from the browser confirmed the live HTML genuinely lacked the script). Second, that saving the variable as an encrypted Secret rather than plaintext Text meant it never reached the npm run build step (it didn’t matter; the Secret-type variable worked fine once a fresh build actually ran). The real fix was simpler than either theory: a build variable doesn’t retroactively apply to a build that already ran, and Cloudflare’s Retry build triggers a genuinely new one.

Variations

  • Self-hosted or EU-cloud PostHog. An equivalent host variable, alongside your key variable, lets you override PostHog’s default US endpoint, so a self-hosted instance or PostHog’s EU cloud region both work without touching the init code.
  • Server-side capture. The current setup is client-side only, so it’s subject to ad blockers like any other browser-based analytics. A server-side capture call would be more resilient, but it’s a separate step from getting a first, working traffic signal.
  • Next in this line. The repo route pulls this data into the repo on a schedule and renders it as a live dashboard at /dashboard, without a server or a client-exposed read key. After that, once real traffic data accumulates, it becomes a missing input for the Growth & Marketing AI channel’s attribution work: the dynamic ICP model reasons about conversion and revenue by segment, but nothing on this site yet reasons about which channel a reader arrived from.

Nathaniel Stich writes Exit Velocities — recipes drawn from AI/growth systems actually shipped in production, not opinion pieces.

About the author