Week of Jul 14 → Jul 20. Receipts, not lessons. The headline is a full framework upgrade; the rest is money-path hardening + SEO recovery. Every item cites the commit/tag it shipped under.
We upgraded the entire build stack — Astro 5 → 7 and Tailwind 3 → 4 — and it's live on prod (prod-2026-07-18-001) with zero regressions. This was the big one and it took the time it did because we did it right: staging-first, measured pixel-parity, and 3 latent-bug fixes caught before any user hit them. Alongside it: the domain-broker lead form got real abuse protection, a broken Hide.me affiliate link got fixed, and ~1,600 orphaned translated URLs are being recovered from 404s.
Upgraded the whole front-end toolchain in one controlled run:
@tailwindcss/vite plugin replacing the now-deprecated @astrojs/tailwind integration, @import "tailwindcss" + @config compat.staging.vpn.com (behind the workers) → promote.sh to prod. Rollback is one command (rollback.sh / prior prod tag).@layer cascade bug that was silently inverting utility priority (Preflight was beating our own utility classes) — fixing it took the a11y audit from 98 → 0 serious findings. We also hardened the a11y gate in the process.Technical honesty for the room: the "why did this take a while" is the validation rigor, not thrashing. TW4's Preflight changed several defaults out from under us (border-color → currentColor, the @layer cascade order, button{cursor:pointer} dropped). Each is a silent, site-wide regression invisible to the build — only a rendered diff + a11y audit catches them. We found and fixed all three (border-color compat, cascade order, cursor) before promote. That's the work.
(Commits 297c78ad→33b1fc48; deploy prod-2026-07-18-001. Full log: ops/astro-7-tailwind-4-migration.md.)
Provider review pages — redesigned
f5d3a8a3, 97d39fcf, d0bdc8ae, 460d7371, 520dff01). The provider review pages (our biggest affiliate surface after the hubs) got a real UX pass: a unified TrustScore hero (rating + score + metric breakdown in one component), a two-column assessment (summary scorecard beside the editorial), a per-section rank on every scorecard, and real progress bars + a ratings strip. Plus scroll-jump/flicker fixes on expand (3f2cf843, 4cfe59bc) and Fable polish (bar parity, focus rings, tap targets). The 15 assessment claim headlines were translated across 17 languages (21ec1de4), so the redesign shipped fully localized, not English-only.Domain-broker V2 lead form — launched
vpn-workers c53e494 + hero-v2). Structured multi-domain intake (per-domain buy/sell + budget), a typed e-signature, and a submit-choice (submit-now vs jump-start) — a real capture upgrade over the old single-field form. Health check: it's capturing the new structured fields cleanly — submit_choice + agreement on 34/34 recent leads (100%). This is the form the hero A/B runs on.f40aa74). Found the rate limiter had a bug that made it never fire for anyone (a .first() destructuring left the count permanently 0). Fixed; switched to 1 request / 10 seconds, keyed per-submitter (IP → email → session) so it stops rapid duplicate/bot submits without ever blocking a legit multi-domain seller. Also fixed IP capture (was empty on 444/444 leads) via an X-Forwarded-For fallback. Validated live: repeat → 429, window clears after 10s.51dc3acd, worker + both form variants) — a real seller listing a portfolio now submits half as many batches.aaef921, bf20a3a): the typed signee name + signed-at timestamp now reach GHL; the lead page shows a ✓ before "signed", a "Submission method" label, and a Total-discount row.Domain-broker hero A/B — system hardened + live
Activated on prod, 50/50 (vpn-workers 96107a2, HERO_AB_ACTIVE=true, Jul 17). The A/B engine itself got real refinement first: dual-cached + contamination-proof (no scenario where B serves a cached-A page but reports as B — the exact false-positive we were worried about), host-aware activation (always on staging.vpn.com, gated on prod), an honest kill switch, and control-arm exposure tagging so both arms are measurable, not just the treatment. Unified results at /api/ab-stats (feeds the editor + askbowtie). (dbd389b, ac36dd54, d5bcb07, cc71fbe.)
Pulse — B holds its own, which is exactly what this test was built to check. This was a guardrail test ("does the new hero do significantly worse?"), not a precision winner-pick. On that bar it passes: deduped to unique real submitters (excluding test emails) it's A=4 / B=4, dead even, with no sign B underperforms. (Raw A=9 / B=38 is inflated by legit high-volume sellers — one 75-domain portfolio = 16 submissions under one arm — not spam, so no leads were purged.) Keep it running for a cleaner, longer window. (Minor measurement note: a true per-arm conversion rate needs the view count joined to the leads; small worker fix teed up. Doesn't change the read above.)
Measurable win — the V2 form beat V1 on engagement. We can compare the form itself pre/post its Jul 13 rebuild. Measured by share of viewers:
| Funnel stage | V1 (Jun 22–Jul 9) | V2 (Jul 14–20) |
|---|---|---|
| Unengaged (viewed, touched nothing) | 50.6% | 41.3% |
| Engaged | 49.4% | 58.7% |
| Reached domain/budget entry | 21.9% | 21.2% |
| Reached confidential / e-sign | 8.3% | 7.3% |
V2 cut the unengaged rate 51% → 41% — nearly 1 in 10 more visitors now interact with the form, and the mid/deep funnel held flat despite V2 being longer (multi-domain + typed e-sign + jump-start branch). So "longer form = more abandonment" is not borne out. Note raw bounce looks flat/worse (83% → 85%), but bounce is a poor fit here: a single-page multi-step form registers as a "bounce" even when someone works through three steps, so it penalizes V2 for being a good in-page form. The unengaged rate is the honest read. (Windows are unequal length, so treat the 10-pt gap as directional.) Jump-start is also landing: choice split ~even (19 vs 17), and jump-start users fill 11–22 enrichment fields (why_now, escrow_ready, appraisal_expectation…) — completed, not abandoned.
Affiliate money-path fix
2e11c138, prod-2026-07-19-001). hide-me.json linked /out?partner=hideme while the entire /out worker keys hide-me — so Hide.me clicks split attribution, missed the free-compare gateway, and the fallback dead-ended buyers on a 404 (/vpn/hideme/). One-line slug fix, verified live.Conversion tracking + lead-capture reliability
64072aed, vpn-workers 6cb6344). affiliate_click and perks_signup now fire from the server (off the D1 write) instead of the browser, deduped for ~1:1 parity with the D1 record. This is why the conversion numbers in the snapshot below are trustworthy — client-side firing was double-counting and dropping events; the count is now the source of truth, not an estimate.vpn-workers 901443d) — we were quietly leaking real leads. Separately, repaired a cross-page translation contamination and closed the generator hole that caused it (a8444829) — the same class of bug that once bled one page's copy into another across 7 languages.SEO / redirects (askbowtie audit + GSC 404 recovery)
/device/linux/* redirects).8a50e18d). Diagnosed the GSC "Not found" report: the June-24 slug-map centralization + repeated bulk re-translations changed live translated slugs (e.g. miglior-vpn-gratis → -gratuito), orphaning URLs Google had indexed. Added 136 verified old → new 301s (the ones the earlier 341-redirect flip missed) — old 404 → new 200, firing for Googlebot + browser.build.sh) already prevents the re-slug from ever recurring.1. Add 5 new languages — Hebrew, Russian, Thai, Norwegian, Polish. Infrastructure is ready (scripts + preflight gate). The rollout is a multi-hour bulk-translation job (1,483 pages × 5 langs, Claude API spend) — needs a green-light to run. (Carried from the Jul 14 meeting; unchanged.)
2. English legacy-404 subset. Correcting the record: not all old English 404s are "correctly dead." A real 301-worthy subset is unhandled (/vpn/xbox/, device/router/*, gaming-vpn/* → their current /vpn/best-vpn-for-* equivalents). Worth a follow-up run — harder than the translated set (no deterministic slug-map; needs pattern-matching). Decision: prioritize it or not?
3. Redirect-worker hardening (kv.js). The worker's own single-add path (upsertRedirect) re-breaks index density on the next admin-side redirect add. Low-effort guard, worth scheduling before the next manual redirect edit.
4. Migration certainty window. 2 days of clean data (speed ↑, conversions flat, no new errors) — but a true engagement/SEO read needs 7–14 days + GSC Crawl-Stats / Core-Web-Vitals. Can auto-pull at the 1-week mark.
| Metric | Value | Read |
|---|---|---|
| Conversions | 724 @ 11.7% | Up wk/wk, 38% above 4-wk avg — but partly the server-side-conversion + A/B changes, not the migration. Rate holding post-migration = no regression. |
| Speed (post-migration) | LCP p50 1.38s · crawler TTFB 249ms | Faster than the pre-migration baseline. ✅ |
| Errors | No new signatures since 7/18 | Only "critical" is one pre-existing 85-second transient blip. ✅ |
| Money pages | /out 64% · /vpn/best-free-vpn 36% · /domain-broker 34% | Healthy; translated free-VPN pages converting 44–57%. |