Day zero. No features yet. Already measuring.
I had a blank canvas. Answerdeck, a compliance intelligence platform I’m building solo, existed as a skeleton: a consent banner, a page-view counter from the analytics provider, and exactly zero custom events. Zero identify calls. Zero funnels. The standard "we’ll add tracking later" state that every early-stage product ships in.
I was about to start building the first real feature. And I stopped myself. Because I’ve seen what happens when you bolt observability onto a product that already has users. You end up debugging your telemetry in production, staring at dashboards that don’t answer the questions you actually have, and retrofitting event names onto flows that were never designed to be measured.
So the first night wasn’t features. It was measurement infrastructure. Every event planned. Every privacy constraint encoded. Every layer defined before a single line of product code existed.
A structured telemetry lifecycle
Before writing a single analytics event, I ran a five-step process. Not a brainstorm on a whiteboard. A structured lifecycle with written outputs at every stage:
- Model the product: map every entity, every user action, every state transition the product will eventually support.
- Audit the current state: what does the analytics provider already capture out of the box? What’s missing?
- Design the tracking plan: name every event, define every property, assign every event to a layer.
- Generate the implementation guide: translate the plan into concrete code patterns, typed wrappers, and privacy guardrails.
- Implement: write the code, wire the events, verify end-to-end.
The output was a versioned folder of artifacts describing what to track and why. Not a Notion page that drifts out of date. Every decision has a written rationale. When future-me asks "why do we track this event?" the answer is in the plan, not in someone’s memory.
Finding the one event that proves the product works
Every product has a primary value action. One event that, if it stops happening, means the product has failed. For a compliance tool like Answerdeck, that event is the moment a subject-matter expert approves an AI-drafted answer. Not when the AI generates the draft. Not when the user opens the question. The approval. That’s the signal that the product delivered real value: a human trusted the output enough to stamp it.
If that metric drops to zero, nothing else matters. Funnels, cohort analysis, group analytics, and retention curves all hang off that single signal. Design your tracking plan around the primary value action, and everything else falls into place.
I started with roughly 30 events total. Small on purpose. Enough to measure every critical path. Not so many that the tracking plan becomes a maintenance burden before the product has its first user.
GDPR made most of my design choices for me
There’s a difference between "EU-compatible" and "EU-first." Compatible means you build what you want and then add a consent banner and hope your data processing agreements hold up. EU-first means the regulation shapes the architecture from the start. I chose the second path.
The consequences are concrete:
- EU-hosted analytics: all product analytics data lives in Frankfurt. No transatlantic data transfers for user behaviour data.
- No autocapture until consent: the analytics SDK does not fire automatic events until the user explicitly opts in. Default is off.
- Session replay masks everything: all text inputs, all form fields, all content areas are masked by default. You see the interaction pattern, not the data.
- No raw content in event properties: if an event needs to reference a document or a user input, it stores a cryptographic hash. Never the actual content.
- Consent banner respects Do Not Track: if the browser sends a DNT header, the banner defaults to opted out. The user can still opt in, but I’m not going to pretend I didn’t see the signal.
These aren’t features. They’re regulation. The only question was whether to enforce them at the architecture level or hope everyone remembers the rules. I chose architecture.
Four layers, zero overlap
Observability is not one thing. It’s at least four, and they should never bleed into each other. I split the entire telemetry stack into four layers, each owning exactly one concern:
| Layer | Role |
|---|---|
| Product analytics | Custom events, funnels, cohort analysis, session replay |
| Error tracking | Exception capture, automatic grouping, alerting |
| Page analytics | Page views, referrers, Web Vitals |
| Compliance event stream | Append-only audit logs, multi-year retention, tamper-evident |
Each layer has its own provider, its own data model, its own retention policy. Product analytics answers "what are users doing?" Error tracking answers "what broke?" Page analytics answers "how does the site perform?" The compliance stream answers "what happened, provably, for the auditor who asks in three years?"
No overlap. No gaps. When something goes wrong, I know exactly which layer to query. When a regulator asks for an audit trail, I don’t have to grep through product analytics events hoping I logged enough context.
PII boundaries enforced in code, not by convention
"Don’t log PII" is a policy. Policies get violated at 2am when you’re tired and just want to debug a production issue. I needed something stronger.
So I built typed wrappers around the analytics SDK. Every event function accepts a strictly typed payload. The type system includes a blocklist of sensitive fields: raw prompts, email addresses, document content, anything that could identify a user or expose their data. You physically cannot pass these fields as event properties. The TypeScript compiler rejects it.
trackEvent('answer.approved', { answerId: hash, durationMs: 340 })
// This does NOT compile — 'content' is in the PII blocklist:
trackEvent('answer.approved', { answerId: hash, content: rawText })
// ❌ Type error: 'content' is not assignable to SafeEventProperties
Internal users, meaning team accounts, test accounts, and my own sessions, are excluded from analytics automatically. Not through a filter in the dashboard. Through a guard at the SDK layer that checks before any event is dispatched. If you’re an internal user, the event never fires.
This is stronger than "remember not to log PII." This is the codebase won’t let you log PII, even if you try.
Environment hardening
I audited every environment variable that reaches the browser bundle. In a client-side app, anything prefixed for client access is visible to anyone who opens DevTools. That’s by design. But it means you have to be deliberate about what gets that prefix.
The rule is simple: only public API endpoints and project-scoped keys, things designed to be public, get the client prefix. Sensitive tokens, service role keys, anything with elevated permissions lives server-side only. Never prefixed. Never bundled.
I grepped every source file. Zero sensitive variables leak to the browser. This is the kind of audit that takes fifteen minutes on day one and saves you from a front-page incident on day one hundred.
One command ships a step
I built a single command that runs the full shipping pipeline: update the tracking plan, validate that documentation is current, run type checks and security scans in parallel, and mark the step as complete. One invocation. Done.
The important thing is I built this before I needed it. Before there were features to ship. Before there was a tracking plan to validate. The pipeline existed first. Now every future feature, from the first one to the hundredth, ships through the same automated sequence.
Most teams build their CI/CD pipeline after the third time someone ships a broken build. I wanted the opposite: the pipeline enforces quality from commit one. Type-checking, linting, security scanning, documentation validation: all running in parallel, all blocking the ship if anything fails.
This matters more for a solo founder than for a team. On a team, someone might catch your mistake in code review. Solo, there is no one to catch it. The pipeline is the reviewer. It never gets tired. It never says "looks fine" because it’s Friday afternoon.
Greenfield is underrated. The cost of setting up tracking v1 is a tenth of what it would be in six months.
Three lessons from wiring observability on day one
1. Minimalism is cheap to add, expensive to remove
I started with roughly 30 events, not 60. Every event I didn’t add is an event I don’t have to maintain, document, or explain to a future teammate. Adding a new event when you discover you need it takes five minutes. Removing an event that polluted your funnels for six months takes hours of cleanup and retroactive analysis. Start small. Expand deliberately.
2. The lifecycle matters more than the events
The specific events I chose are less important than the process I used to choose them. Having a written rationale for every tracking decision means I can revisit any event six months from now and understand exactly why it exists, what question it answers, and whether it’s still worth capturing. Without that rationale, tracking plans become archaeological sites: layers of sediment no one dares to remove because no one remembers what they’re for.
3. Automation before scale
The shipping pipeline exists before the features do. The privacy guards exist before user data does. The compliance layer exists before the first audit. This feels premature. It isn’t. Every hour I spend on infrastructure now saves ten hours of retrofitting later. And unlike feature code, infrastructure work compounds. The pipeline I built tonight will run thousands of times.
AI didn’t replace me. It multiplied me.
I did everything you’ve just read alone. The telemetry lifecycle, the privacy architecture, the four-layer separation, the shipping pipeline, the environment audit. One person. One night.
AI tools were involved. I won’t pretend otherwise. But here’s the honest take: AI did not make the architectural decisions. I did. AI did not decide that PII boundaries should be enforced at the type level. I did. AI did not know which event was the primary value action. I did.
What AI did was remember the rules I wrote and apply them consistently across every file. I wrote the constraints. The tools enforced them. That’s the difference between vibe-coding and vibe-engineering: having a system that enforces your own standards when you’re tired at 2am.
I set the rules. I encoded the philosophy. I made the hard calls about what to measure and what to leave out. Then I let the tools do the repetitive, detail-oriented work of applying those decisions everywhere. That’s not replacement. That’s leverage.
Written by withkarann · April 2026 · withkarann.com