2 — The audit method
Tours the eight finding clusters across the running app and its source, then writes findings/007-missing-priority.md end to end as the chapter’s reference shape.
You have spent this unit learning the two halves of running a SaaS in the dark and turning the lights on: the wiring half — Sentry catching errors, structured logs you can read at 3am, PostHog gated behind consent — and the vigilance half — Core Web Vitals, the bundle analyzer, RSC waterfalls, N+1 queries. This project closes the unit by running both halves against one seeded app the week before it launches. The audit target is the same invoicing SaaS you have been growing across the course, but this branch ships with its observability wiring deliberately missing or broken and four performance regressions planted in the code. Your job is a hybrid wire-and-document audit: the observability gaps lose data the moment a user hits the app, so you fix them — you wire Sentry, harden the logger, and build the consent gate. The performance gaps are slow, not bleeding, so you document them — you write a findings report with measured impact rather than patching live code, with one exception: you fix the barrel import in place so you can capture the bundle-analyzer before and after as evidence. That split is the whole point. An experienced engineer at a launch review does not treat every problem the same way; a leak that drops error data closes before launch, a slow query goes to the backlog with a number attached.
By the end, the finished audit produces four artifacts — the four things an experienced engineer confirms at a launch review. None appears above: each lives on a different surface you drive yourself — Sentry’s dashboard, PostHog’s dashboard, the Turbopack analyzer, and a Markdown file — so you will not see them side by side until you have run each tool against the app above.
GET /api/test/throw route — tagged with the release matching the current commit, carrying a readable source-mapped stack trace.$pageview that fired only after the user clicked “Accept” on the consent banner, and nothing before it.lucide-react barrel fix — the oversized icon tile shrinking sharply once optimizePackageImports is in place.findings/SUMMARY.md quantifying coverage across the eight findings, with the two bonuses called out.EXPLAIN ANALYZE, then writing them up with the rule-location-consequence-fix template.The audit installs four clusters of wiring and audits four performance surfaces, then collects everything into one deliverable. This is a config inventory, not a request flow — the shape of what gets wired and what gets audited, nothing about how yet. Each piece is the work of a later lesson.
instrumentation-client.ts (client init) plus sentry.server.config.ts and sentry.edge.config.ts, booted by instrumentation.ts and wrapped into the build by withSentryConfig in next.config.ts. Source maps upload only when SENTRY_AUTH_TOKEN is present at build time; the release tag is computed from VERCEL_GIT_COMMIT_SHA with a 'dev' fallback.src/lib/logger.ts carrying a redact drop-list and a requestId mixin. src/proxy.ts mints an x-request-id (uuidv7()) and opens a runWithContext scope over an AsyncLocalStorage defined in src/lib/request-context.ts; each downstream seam — the Stripe webhook handler, for instance — recovers that ID from the header and opens its own scope, which both the logger and Sentry’s beforeSend read.posthog-js loaded inside a PostHogGate in src/app/_components/providers.tsx, with capture off by default, gated by src/lib/analytics/consent.ts behind a ConsentProvider and a consent banner you build from scratch.src/app/(protected)/dashboard/page.tsx), the authenticated layout (src/app/(protected)/layout.tsx), the marketing hero (src/app/(marketing)/page.tsx), and the dashboard’s invoice-with-customer read (src/db/queries/invoices-with-customer.ts) — audited through DevTools Performance, the Turbopack analyzer, and EXPLAIN ANALYZE.findings/, holding the rule-location-consequence-fix template, numbered files for findings 1–10, SUMMARY.md, out-of-scope.md, and screenshots/.The starter is the full invoicing app, so most of the tree is the codebase you already know and will not touch. The annotated files below each carry a seeded finding or are a seam a later lesson extends; everything else is uncommented. The bolded cluster is your focus — the files carrying findings 4, 5, 6, 7, and 8, plus the empty findings/ skeleton you will fill in.
withSentryConfig; no optimizePackageImports (findings 1, 6)x-request-id mint/echo + runWithContext scope yet (finding 3)posthog-js at module scope, capture on, no consent gate (finding 4)<Image> missing eager-load prop (finding 7)<link> font, not next/font (bonus 9)lucide-react barrel import (finding 6)redact + no requestId mixin (findings 2, 3)SUMMARY.md, out-of-scope.md, screenshots/Notice what is not in the tree yet — the files you create as you wire each fix. The Sentry config files (instrumentation.ts, instrumentation-client.ts, sentry.server.config.ts, sentry.edge.config.ts) appear when you wire Sentry; src/lib/request-context.ts lands with the logger seam; and the consent layer (src/lib/analytics/consent.ts, src/app/_components/consent-provider.tsx, src/app/_components/consent-banner.tsx) is built from nothing when you gate PostHog. The starter ships none of them — that absence is finding 1, finding 3, and finding 4.
2 — The audit method
Tours the eight finding clusters across the running app and its source, then writes findings/007-missing-priority.md end to end as the chapter’s reference shape.
3 — Wire Sentry
Installs Sentry across client, server, and edge with source maps and a release tag, so the deliberate throw lands decoded in the dashboard.
4 — The production logger seam
Adds the single redactor reused by Pino and Sentry’s beforeSend, plus a request-correlation-ID middleware backed by AsyncLocalStorage.
5 — Gate PostHog behind consent
Flips capture off by default and routes accept and reject through one consent seam, so events fire only after opt-in.
6 — Document the performance findings
Writes the waterfall and N+1 findings, fixes the barrel import in place for the bundle-analyzer before/after, and assembles SUMMARY.md.
7 — Verify and self-grade
Runs the full verify recipe one surface at a time, commits, then diffs the work against the solution/ answer key to score coverage.
This starter has more moving parts than earlier projects — a Postgres database to migrate and seed, a real sign-in to get past the dashboard guard, and a pair of optional third-party accounts if you want to watch live events flow. The third-party keys are genuinely optional: the dummy values in .env.example pass validation with no network round-trip, so the app boots fully without them. You only need real keys when you reach the lessons that confirm events landing in a dashboard.
Get the starter codebase from the project repository, under Chapter 095/start/.
Install dependencies.
pnpm installCopy the environment template. The migrate and seed scripts read .env; next dev reads .env.local, so create both.
cp .env.example .envcp .env.example .env.local(Optional) Populate the real Sentry and PostHog keys if you want to see live events. The dummy values already in the file pass validation, so skip this for now and come back when a lesson asks for it.
Start the Postgres container.
docker compose up -dRun the migrations.
pnpm db:migrateSeed the database.
pnpm db:seedStart the dev server.
pnpm devOpen http://localhost:3000/sign-in and sign in as the seeded admin: alice@example.com / inspector-password-12 (the SEED_PASSWORD constant in scripts/seed.ts).
The optional third-party keys, when you reach the lessons that need them:
| Variable | Purpose | How to obtain |
| --- | --- | --- |
| NEXT_PUBLIC_SENTRY_DSN | The client/server endpoint Sentry events are sent to. | Create a free-tier Sentry org and project; the DSN is under Project Settings → Client Keys. |
| SENTRY_ORG, SENTRY_PROJECT | Identify the project the build uploads source maps to. | The org and project slugs from your Sentry account. |
| SENTRY_AUTH_TOKEN | Gates source-map upload at build time. | Settings → Auth Tokens, scoped to allow source-map upload. |
| NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_HOST | The PostHog project key and ingestion host. | Create a free-tier PostHog project; both values are on the project’s setup page. |
Expected result. The app boots on http://localhost:3000 with a marketing landing page, an authenticated dashboard, and an invoice list seeded under org_acme — roughly 30 customers, 240 invoices, and three or more members. The /dashboard route requires a real Better Auth session; without one, proxy.ts redirects you to /sign-in, which is why step 9 signs you in. At this point everything is running in its broken starting state: Sentry is unwired so errors vanish, the logger leaks the Stripe signature in the clear, PostHog captures on first load before any consent, and the four performance regressions are all live. That is exactly the state the rest of the chapter resolves.