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:
| Event | What it captures | Where |
|---|---|---|
contact_form_submitted / contact_form_failed | Form conversions and server errors | Contact.astro |
contact_link_clicked | Email / LinkedIn / GitHub outbound clicks | Contact.astro |
contact_project_type_selected | Project-type chips toggled in the form | Contact.astro |
work_card_clicked | Clicks out to client project sites | Work.astro |
blog_post_viewed | Post loads, with title/tag/read-time properties | blog/[slug].astro |
blog_load_more_clicked | Pagination engagement on the blog index | BlogList.astro |
hero_cta_clicked | Which hero CTA converts, with label + href | Hero.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:
| Product | What it gives you | Where it fits here |
|---|---|---|
| Feature flags | Remote toggles + percentage rollouts, evaluated client-side | Ship 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 testing | Test two hero CTAs against the hero_cta_clicked event that’s already instrumented |
| Session replay | Recordings of real visits, console + network included | Watch where contact-form visitors stall instead of guessing from funnel drop-off |
| Surveys | Targeted in-app prompts, no extra tooling | Ask “what were you looking for?” on blog exit |
| Error tracking | Client-side exceptions tied to sessions and users | The wizard’s checklist already nudges toward this with source-map upload |
| Web analytics | GA-style traffic dashboard, privacy-friendlier | The 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:inlineblock — correct for Astro, but it means aneslint-disableisland in the codebase and no tree-shaken, typedposthog-jsimport. 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.