Skip to main content
← Back to writing

The Analytics Setup That Wrote Itself: Evaluating PostHog's AI Wizard

TL;DR: I pointed PostHog’s AI setup wizard at this very site — an Astro build with View Transitions, a combination that quietly breaks most analytics snippets. It shipped a working integration in minutes: an init component with a ClientRouter re-execution guard I would have discovered the hard way, 8 custom events across 6 files that map to real visitor journeys, typed window declarations, a written setup report, and a pre-built dashboard with 6 insights waiting in PostHog. Bonus: the snippet it installed is the on-ramp to the whole platform — feature flags, A/B experiments, session replay, surveys — with no second vendor or script. The catch: it stops at the happy path — the ad-blocker-resistant reverse proxy was on me. Verdict: 🟢 Must-try — the rare AI codegen that understands your framework’s failure modes better than its marketing page does.

🎯 The Problem: Analytics Is Always the Chore You Defer

Every portfolio site owner has the same blind spot: you ship, you share the link, and then you have no idea what happens next. Does anyone click the work cards? Do blog readers ever reach the contact form? I’d deferred instrumenting this site for months, because analytics setup is death by a thousand small decisions — which events, what property names, where in the codebase, how to survive client-side routing.

That last one is the real trap here. This site uses Astro’s View Transitions (ClientRouter), which means soft navigations re-execute inline scripts. Drop a standard analytics snippet into the layout and every page transition re-initializes it — in PostHog’s case, that manifests as a stack overflow. It’s exactly the kind of framework-specific landmine that generic “paste this in your <head>” docs never mention.

So instead of hand-rolling it, I let PostHog’s AI wizard take a shot at the whole thing:

npx -y @posthog/wizard

🤖 What the Wizard Actually Did

This is not a config generator that spits out a snippet and wishes you luck. It’s an agent: it read the codebase, identified the framework and the routing mode, planned an integration, edited files, and wrote a report of everything it touched.

flowchart LR
    A["Detect stack<br/>(Astro + ClientRouter)"] --> B["Create init component<br/>with re-init guard"]
    B --> C["Instrument events<br/>(6 files, 8 events)"]
    C --> D["Types + env<br/>scaffolding"]
    D --> E["Build dashboard<br/>+ 6 insights"]
    E --> F["Write report +<br/>leave agent skill"]

The centerpiece is a posthog.astro component wired into the base layout — and the detail that earned my respect is right at the top of it:

// Guard against multiple initializations during view transitions.
// Without this guard, ClientRouter's soft navigation re-executes inline
// scripts, causing a stack overflow error.
if (!window.__posthog_initialized) {
  window.__posthog_initialized = true;
  // ... snippet + posthog.init with capture_pageview: "history_change"
}

Two framework-aware decisions in one place: the initialization guard against ClientRouter’s script re-execution, and capture_pageview: "history_change" so soft navigations still count as pageviews. Either one missing and the integration is silently broken — the first crashes, the second undercounts every navigation after the landing page.

📊 Eight Events That Actually Map to Journeys

The part I expected to redo by hand was event selection. I didn’t have to. The wizard read the components and derived the instrumentation from what the site does, covering the three journeys a portfolio actually has — contact conversion, work engagement, and content consumption:

EventWhat it capturesWhere
contact_form_submitted / contact_form_failedForm conversions and server errorsContact.astro
contact_link_clickedEmail / LinkedIn / GitHub outbound clicksContact.astro
contact_project_type_selectedProject-type chips toggled in the formContact.astro
work_card_clickedClicks out to client project sitesWork.astro
blog_post_viewedPost loads, with title/tag/read-time propertiesblog/[slug].astro
blog_load_more_clickedPagination engagement on the blog indexBlogList.astro
hero_cta_clickedWhich hero CTA converts, with label + hrefHero.astro

And the calls it wrote are the calls I’d want to maintain — defensive, property-rich, in the site’s existing style:

window.posthog?.capture("blog_post_viewed", {
  title: postTitle,
  tag: postTag,
  read_time: postReadTime,
  slug: postSlug,
});

It also did the unglamorous scaffolding humans skip: Window type declarations in env.d.ts so window.posthog type-checks, and env vars documented in .env.example.

🎁 The Leave-Behinds Nobody Else Ships

Two things separated this from every other codegen tool I’ve evaluated.

It built the analysis layer, not just the capture layer. When the wizard finished, a dashboard with six pre-built insights was already live in PostHog — contact-form submissions, blog views over time, portfolio engagement, outreach clicks by type, and a hero CTA → form submit funnel. Instrumentation without insights is homework; this arrived with the homework done.

It left an agent skill in the repo. The wizard dropped a .claude/skills/integration-astro-view-transitions/ folder — structured, current context about PostHog + Astro + View Transitions that Claude Code automatically picks up in future sessions. My AI tooling now knows how this site’s analytics works, from the vendor, kept out of the LLM’s stale training data. That’s a genuinely new pattern: the integration teaching the next agent that touches it.

It even wrote its own post-merge checklist into a posthog-setup-report.md — run the production build, wire source-map upload into CI, update .env.example. An AI tool that documents its own loose ends is rarer than it should be.

🧰 One Snippet, a Whole Platform

Here’s the strategic part that changes the math on the setup: the loader the wizard installed isn’t just an analytics tracker — it’s the entry point to PostHog’s entire product suite, on the same script, the same event stream, and (for a site this size) the same free tier. No second vendor, no second snippet, no second consent conversation:

ProductWhat it gives youWhere it fits here
Feature flagsRemote toggles + percentage rollouts, evaluated client-sideShip a redesigned hero or a new section behind a flag, roll it out gradually instead of big-bang
Experiments (A/B)Flag-backed variants with built-in significance testingTest two hero CTAs against the hero_cta_clicked event that’s already instrumented
Session replayRecordings of real visits, console + network includedWatch where contact-form visitors stall instead of guessing from funnel drop-off
SurveysTargeted in-app prompts, no extra toolingAsk “what were you looking for?” on blog exit
Error trackingClient-side exceptions tied to sessions and usersThe wizard’s checklist already nudges toward this with source-map upload
Web analyticsGA-style traffic dashboard, privacy-friendlierThe zero-effort baseline under the custom events

The compounding detail: the stub the wizard installed already exposes isFeatureEnabled, onFeatureFlags, getSurveys, and captureException — the API surface for all of it is already on the page. Turning on session replay or the traffic dashboard is a toggle in PostHog, no deploy required; a first feature flag is a few lines against an SDK that’s already loaded.

That’s what makes the instrumented events more than a reporting layer. hero_cta_clicked isn’t just a chart — it’s a ready-made goal metric for the first experiment. The funnel the wizard built isn’t just a dashboard — it’s the baseline you compare every variant against. The wizard didn’t set any of this up, but it laid the rails: on most analytics stacks, “let’s A/B test the hero” is a new-tool conversation. Here it’s an afternoon.

⚖️ Where It Falls Down

An honest evaluation has to name what I still did myself.

  • It stops at the happy path. The generated setup pointed straight at PostHog’s cloud host — which ad blockers eat for breakfast. Routing capture through a first-party reverse proxy on my own domain was a follow-up commit the wizard neither did nor suggested. For a portfolio aimed at a developer audience (the most ad-blocked demographic on earth), that’s not an edge case; it’s the difference between data and fiction.
  • It ships the inline snippet, not the SDK. The integration is the minified loader script in an is:inline block — correct for Astro, but it means an eslint-disable island in the codebase and no tree-shaken, typed posthog-js import. Fine for a static site; I’d want the package on anything larger.
  • Trust, but diff. It edited six files across the codebase. Every change it made was one I’d have approved in review — but I only know that because I did the review. The report tells you what it touched; it doesn’t absolve you from reading it.

✅ The Verdict

🟢 Must-try. The bar for AI codegen isn’t “produces code” anymore — it’s knows the failure modes of your specific stack. This wizard cleared that bar: the ClientRouter guard and history_change pageview mode are the kind of details you usually learn from a GitHub issue at midnight, and it had them on the first pass. Add the derived-from-your-actual-components event schema, the pre-built dashboard, and the agent skill it leaves for future AI sessions, and it delivered in minutes what I’d been deferring for months.

The proxy gap is real, and you still own the review. But that’s the right division of labor: the machine does the framework archaeology, you do the judgment.

I put off analytics for months because the setup was tedious. It turns out the setup was the machine’s job all along — mine was just deciding what to do with the answers.