Portfolio performance baseline — 6 surfaces · 2026-05-20

The first portfolio-wide perf-trend run, the day-zero baseline that 90-day comparisons get anchored against. Six public surfaces measured via Google’s PageSpeed Insights Lighthouse lab (synthetic). pa.gf.cx skipped — CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF-Access-gated, lab would have measured the login page rather than the actual content.


TL;DR

Surface Mobile score Mobile LCP Desktop score Desktop LCP Verdict
dare.co.uk100817ms 🟢100409ms 🟢Gold standard — top-tier both
beta.dogwood.house942,866ms 🟡100613ms 🟢Desktop great; mobile LCP needs trim
gf.cx933,157ms 🟡100522ms 🟢Built today — hero photo likely the LCP culprit on mobile
dansellars.com873,631ms 🟡99751ms 🟢Personal landing — mobile LCP NI
bookings.audreyinc.com803,610ms 🟡98850ms 🟢Functions-backed booking form — mobile LCP NI
audreyinc.com785,061ms 🔴931,615ms 🟢Shopify default theme weight — real signal worth Audrey acting on

Two real findings worth acting on:

  1. audreyinc.com mobile LCP at 5.06s is poor tier (Google’s threshold: ≤2.5s good · ≤4s NI · >4s poor). This is the real signal Workstream B (Shopify ↔ GitHub theme integration) was designed to address — once theme is git-tracked, perf-tuning via Liquid/CSS/JS edits becomes a normal commit + auto-deploy cycle.
  2. dare.co.uk’s 100/100 both is exceptional — confirmation that the hand-optimised static-rebuild work (Helvetica system stack, Newsreader for <em> accent, inline SVG, no JS frameworks, image-dimensions-on-every-img, CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF Pages caching) bought what it was supposed to.

The visual — mobile LCP across the portfolio

http://www.w3.org/2000/svg" style="font-family: 'Helvetica Neue', sans-serif; max-width: 100%; height: auto; margin: 1.5rem 0;">

Mobile LCP — Largest Contentful Paint (lower is better)

2.5s · good → 4.0s · poor → 0s 1.5s 2.5s 4.0s 5.5s

dare.co.uk 817ms

beta.dogwood.house 2,866ms

gf.cx 3,157ms

bookings.audreyinc.com 3,610ms

dansellars.com 3,631ms

audreyinc.com 5,061ms

The visual — desktop LCP

http://www.w3.org/2000/svg" style="font-family: 'Helvetica Neue', sans-serif; max-width: 100%; height: auto; margin: 1.5rem 0;">

Desktop LCP — everyone in the green

2.0s · upper bound shown 0s 0.5s 1.0s 1.5s 2.0s

dare.co.uk 409ms gf.cx 522ms beta.dogwood.house 613ms dansellars.com 751ms bookings.audreyinc.com 850ms audreyinc.com 1,615ms


What “synthetic lab metrics” actually means

Yes — sci-fi is the right framing. Here’s literally what happens behind every row in those tables:

  1. Google spawns a virtualised Chrome browser in one of their data centres
  2. For mobile: it emulates a Moto G Power (a mid-range 2020s Android phone — deliberately not a flagship, because Google’s measuring what real mid-range users see). CPU throttled 4× slower than the data-centre hardware. Network throttled to “Slow 4G” — about 1.6 Mbps down, 750 Kbps up, 150ms round-trip-time
  3. For desktop: standard Chrome on “Dense 4G” — about 10 Mbps down, 40ms RTT
  4. The virtualised Chrome loads the URL at that throttled speed, exactly once
  5. Lighthouse’s audit suite runs alongside the page load — instrumenting every paint, every script execution, every layout shift, every byte transferred
  6. It returns the numbers within ~30 seconds

So when you read bookings.audreyinc.com · LCP 3,610ms on the table, it literally means: “a robot Chrome browser in a Google datacenter, pretending to be a Moto G Power on slow 4G, just loaded bookings.audreyinc.com and the largest content element appeared on its virtual screen 3,610 milliseconds after the request.”

There is a phantom Chrome out there in the world right now, loading every surface in our portfolio, on demand, with a stopwatch.

Synthetic vs field — when each works

Synthetic (PSI lab) Field (CrUX)
Who’s measuring Robot Chrome in Google data center Real Chrome users in the wild
When On-demand, one page-load per call Continuous, aggregated p75 over 28 days
Conditions Standardised throttle (deterministic) Whatever real users have (diverse)
Coverage Any URL on the internet Only origins with enough Chrome traffic (~thousand uniques/28d)
Today ✅ Works for all 6 surfaces ❌ Below threshold for all 6

CrUX field data would be richer (real users, p75 from real diversity), but our surfaces are all sub-threshold today. Synthetic gives us a deterministic baseline we can track over time. As traffic grows, the script will automatically prefer CrUX field data when it becomes available — no code change needed.


Per-metric thresholds (Google’s official Core Web Vitals)

Metric What it measures 🟢 Good 🟡 Needs improvement 🔴 Poor
LCP Largest Contentful Paint — when the biggest content element appears ≤ 2.5s ≤ 4.0s > 4.0s
INP Interaction to Next Paint — responsiveness to clicks/taps (lab proxy: Total Blocking Time) ≤ 200ms ≤ 500ms > 500ms
CLS Cumulative Layout Shift — how much things move during load ≤ 0.10 ≤ 0.25 > 0.25
TTFB Time to First Byte — server response time ≤ 800ms ≤ 1800ms > 1800ms
FCP First Contentful Paint — when ANY content appears ≤ 1.8s ≤ 3.0s > 3.0s

The mobile-LCP 2.5s line is the most consequential threshold — it’s a Google SEO ranking signal as of 2024.


What this baseline tells us

Two-tier portfolio shape

Tier 1 — 🟢 mobile-good (the gold standard): - dare.co.uk — 817ms · 100/100 · the static-archive build is exemplary

Tier 2 — 🟡 mobile needs-improvement (the workable middle): - beta.dogwood.house — 2.87s · likely the hero image (per the dogwood-agent-stack build today) - gf.cx — 3.16s · the Newsreader font + dan-audrey hero photo loading (just built today) - dansellars.com — 3.63s · older personal landing, untouched in a while - bookings.audreyinc.com — 3.61s · Functions backend doesn’t slow LCP; likely a font/CSS payload trim opportunity

Tier 3 — 🔴 mobile poor (the real opportunity): - audreyinc.com — 5.06s · this is the Shopify default theme + Shopify’s framework JS payload

Where the lever is

The Tier 3 surface is where the Workstream B Shopify-GitHub theme integration earns its keep. Once audrey’s theme is in git, mobile LCP optimisations land via normal commit + auto-deploy: - Defer / async non-critical JS - Inline critical CSS, defer the rest - Optimise hero image (responsive srcset, modern format, preload hint) - Reduce font weights loaded - Audit + remove unused Shopify apps that inject heavy JS

Realistic target after Workstream B: get audrey mobile LCP from 5.06s → ~2.5s. The 90-day trend card will show the lift as the work lands.

Where the lever isn’t

dare.co.uk already wins. Don’t optimise what’s working. Continue editorial work; the perf trend on dare just becomes the canary that catches regressions if/when they happen (e.g., a future image-heavy article that doesn’t follow the existing image-dimensions discipline).


What unlocks from here

Daily run → 90-day trend (automatic)

Schedule the script via launchd or cron at a stable time daily:

# launchd plist or crontab
0 7 * * * /Users/dansellars/bin/dare_perf_trend.py
0 7 * * * /Users/dansellars/bin/dare_perf_trend.py --origin https://www.audreyinc.com
0 7 * * * /Users/dansellars/bin/dare_perf_trend.py --origin https://beta.dogwood.house
0 7 * * * /Users/dansellars/bin/dare_perf_trend.py --origin https://gf.cx
0 7 * * * /Users/dansellars/bin/dare_perf_trend.py --origin https://bookings.audreyinc.com
0 7 * * * /Users/dansellars/bin/dare_perf_trend.py --origin https://www.dansellars.com

After 7 days: “vs last week” columns populate After 28 days: meaningful month-over-month After 90 days: the ~90d delta that Dan asked for becomes real

Dashboard cards for dash.dare.co.uk

The next build is per-surface mini-cards on dash.dare.co.uk showing this shape (here’s audreyinc.com as a worked example):

http://www.w3.org/2000/svg" style="font-family: 'Helvetica Neue', sans-serif; max-width: 480px; height: auto; margin: 1.5rem 0; display: block;">

PERF · MOBILE audreyinc.com

Mobile LCP 5.06s POOR ↓ 0.8s vs 90d

Mobile score 78 /100 ↑ 6 pts vs 90d

Desktop LCP 1.62s GOOD flat

LCP trend · last 90 days

2.5s

today 90 days ago

One card per portfolio surface. Color-coded threshold dot. Inline 90-day sparkline (red line drops below the 2.5s dashed reference = LCP improved across the window). Click-through to the full trend page. Same shape as the existing edge-health cards on dash.dare.co.uk that landed earlier today.

Build trigger: after ~7 days of daily runs accumulate so the sparklines have data to draw against. Until then, today’s portfolio baseline IS the data.

Health hygiene integration (Dan’s framing)

Per Dan 2026-05-20: “It’s a foundational capability that can be across our health/hygiene checks - and clients will love it.”

The perf-trend pattern becomes part of the daily-hygiene tripwire suite — same shape as dare_404_audit.py, dare_header_audit.py, dare_body_image_coverage.py. When LCP crosses the threshold (mobile good → NI, or worse), the daily hygiene report flags it before it becomes a SEO regression in GSC.

Client deliverable

The same script + report shape lifts to any client engagement:

Cost to provide: $0 marginal (free Google API). Cost to client value: meaningful — most SMB sites don’t have any perf tracking discipline, so this becomes a continuous-improvement substrate they couldn’t produce themselves.


Methodology (for the record)


Sibling memories + cross-references


The aphorism

The first measurement is the baseline. The first 100 measurements are the trend. The first 1,000 are the truth. Today is day one — start the clock.

Source: dare_perf_trend_portfolio_baseline_2026-05-20.md · Rendered 2026-05-20 16:43