How we tuned apogee.lu for a 95 mobile PageSpeed score

How we tuned apogee.lu for a 95 mobile PageSpeed score

BackerLeader 4 15 163
calendar_today agoschedule7 min read
— Originally published at apogeewatcher.hashnode.dev

Render-blocking CSS, Cloudflare HTML rewrite, fixed hero heights, and Watcher domain report Summary before and after

Our corporate site at apogee.lu is a static marketing front door: two HTML pages, a hero photograph, Victor Mono typography, and no application server in the path. It should be an easy Core Web Vitals win. Earlier this month, PageSpeed Insights on mobile still reported First Contentful Paint and Largest Contentful Paint above 2.5 seconds, with render-blocking CSS and a filmstrip that stayed blank until late in the load. We build Apogee Watcher to monitor PageSpeed for client estates; tuning our own homepage was overdue, and we used the new domain-level report Summary to keep the work honest across every URL we ship under that host.

Below is the sequence we ran: domain triage, diagnosis, failed experiments, and what landed us at 95 mobile and 100 desktop in lab tests, with Cumulative Layout Shift at 0.

What PageSpeed Insights showed before we changed anything

Mobile lab runs told a consistent story. First Contentful Paint and Largest Contentful Paint were nearly equal, often around 2.6 to 3.0 seconds. The filmstrip showed no painted pixels for most of the timeline, then the full hero appeared in one step. Total Blocking Time stayed at zero; the bottleneck was not JavaScript on the main thread. Lighthouse flagged render-blocking index.css from our asset CDN. Speed Index lagged because the visible hero arrived only after CSS allowed layout.

That single-URL picture was useful, but incomplete. apogee.lu also ships mailops.html and will keep growing small product pages. A green homepage with a neglected second URL is how agencies ship “we fixed PageSpeed” stories that fall apart on the next client call.

How we used Watcher domain reports before touching CSS

Before we edited templates, we generated a domain report for the host and opened it on the Summary tab. Shared domain reports now default to Summary: an attention-free score (the share of completed mobile and desktop tests that pass Performance, category, and Core Web Vitals gates), a short list of metrics that still need attention, and the top pages that fail those gates. For apogee.lu that view made three decisions cheap: the homepage on mobile was the first attention sink (LCP and render-blocking CSS), not a desktop-only quirk; mailops.html either shared the same head and hero pattern or needed its own pass; and a shareable #summary link kept the before state visible while we experimented locally. Single-URL PageSpeed Insights remains the right drill-down once Summary names the URL and strategy. Domain Summary is what stops you from optimising the wrong page first.

How we found the real Largest Contentful Paint bottleneck

Lighthouse named the hero photograph as the Largest Contentful Paint element, but the image was not the first gate. Our 500w WebP hero is about 15 KB and often finished downloading before layout. The gate was synchronous CSS: the browser waited for stylesheet parse before painting.

On production we also found Cloudflare injecting a cdn-cgi/challenge-platform script into HTML. That script added main-thread work and pushed Largest Contentful Paint above 2.9 seconds. Setting Cache-Control: no-transform on HTML from Apache stopped rewrite; injected scripts left view-source and lab Largest Contentful Paint dropped by roughly a second.

Moving assets to our CDN and reordering the document head

We moved images, CSS, JavaScript, and fonts to https://cdn.apogee.lu/assets/ with preconnect in the head. Critical links now run before title and social meta so discovery order matches what lab filmstrips punish: CDN first, then paint-critical assets, then the rest of the document chrome.

  1. preconnect to the CDN
  2. Hero image preload (500w on mobile via <picture>)
  3. Font preloads and victor-mono.css
  4. Main CSS preload and synchronous stylesheet

The hero download starts in the head even though paint still waits on CSS. That parallel fetch is why preload still helps Largest Contentful Paint when the stylesheet remains synchronous.

Hero images and fixed heights per breakpoint

We ship WebP at 500w, 780w, 1200w, and full width. <picture> serves 500w on viewports up to 980px so mobile does not pull a 780w file on a 2x screen. The preload uses media="(max-width: 980px)" so lab mobile requests the smaller asset first.

Fluid height caused Cumulative Layout Shift when we tried async CSS. min-height: clamp(420px, 62vh, 720px) gave 577px on an iPhone 15 Pro Max (62% of 932px viewport height). Late full CSS shifted .hero-stage and Lighthouse scored 0.197 Cumulative Layout Shift.

We replaced fluid height with fixed values from the same formula at canonical breakpoints:

Breakpoint Reference viewport Fixed height
≤980px 390×844 524px
981-1360px 1280×900 558px
≥1361px design cap 720px

Mobile lab Cumulative Layout Shift returned to 0 after deploy. That is the same “reserve space” idea you see in CLS how-tos, applied to a hero stage whose height was CSS-driven rather than an <img> without dimensions. Width and height on the image alone would not have stopped the stage from resizing when late CSS replaced the fluid min-height.

CSS and fonts: what we tried and what we kept

Extracting inline CSS into index.css shrank HTML but did not remove render-blocking.

Inline critical CSS plus async full stylesheet cut First Contentful Paint to about 1.0 seconds locally and cleared the render-blocking warning. It also broke typography (font-display: optional skipped Victor Mono on slow links) and pushed Cumulative Layout Shift back up when below-the-fold rules arrived late. We kept synchronous main CSS and self-hosted Victor Mono on the CDN instead: four latin WOFF2 files and a small @font-face sheet, with preloads for normal and italic latin, and font-display: swap so the correct family appears after files load. The Lighthouse render-blocking warning can remain while Performance still lands in the mid-90s; we preferred stable layout and brand fonts over chasing a green Opportunities row.

Scores after deploy and what Summary showed next

PageSpeed Insights for apogee.lu after deploy: mobile Performance 95 and desktop Performance 100 side by side

Mobile PageSpeed Insights after CDN alignment (captured 30 Aug 2026):

  • Performance 95
  • First Contentful Paint ~1.7s, Largest Contentful Paint ~2.4s
  • Cumulative Layout Shift 0, Total Blocking Time 0ms

Desktop lab reached 100 with sub-second paint metrics (FCP 0.5s, LCP 0.6s, Speed Index 0.7s). Speed Index on mobile can stay above 4 seconds because the hero image paints after text; we accept that on a rarely changed marketing page rather than reopening the async-CSS experiment.

We regenerated the domain report and opened Summary again. All 4 completed page tests passed the CWV and category gates, so the attention-free score sat at 100%, with average Lighthouse Performance 97 on mobile and 100 on desktop.

Apogee Watcher domain report Summary for apogee.lu: site status PASS, attention-free score 100%, four completed page tests within gates

That before/after pair is what we keep when someone asks whether the ship actually moved the portfolio, not only one PageSpeed Insights screenshot.

What we monitor now

We keep apogee.lu on scheduled PageSpeed runs in Watcher and re-check after HTML header, Cloudflare, or CDN cache changes. Lab green after deploy proves the fix shipped. Search Console field data still moves on its 28-day CrUX window, so we do not treat one green lab week as field proof.

Manual PageSpeed Insights before every deploy does not scale across client sites. Apogee Watcher automates runs, stores history, and alerts on regression. Domain reports with Summary give you the same triage shape we used on our own host. For pipeline versus scheduled monitoring, see Lighthouse CI vs managed monitoring.

FAQ

Does CDN CSS stop render-blocking?

No. Synchronous stylesheet blocks first paint until parsed, even for a small file. Preload helps discovery; async or inlined critical CSS removes the gate. We used sync CSS plus fixed hero height to hold Cumulative Layout Shift at zero.

Why preload the hero if CSS blocks paint?

The image can download in parallel with the stylesheet. When paint is allowed, Largest Contentful Paint is shorter if those bytes are already cached instead of starting only after CSS finishes.

Why open a domain report instead of only PageSpeed Insights?

PageSpeed Insights answers one URL. Domain Summary answers which URLs and strategies still need attention under one host, with an attention-free pass rate you can share. Drill into Opportunities after Summary names the page.


Next step: Run PageSpeed Insights on your homepage. If First Contentful Paint equals Largest Contentful Paint in the filmstrip, check render-blocking CSS and injected scripts before resizing images. Generate a free domain scan, open #summary, then start a Watcher trial to keep the same URLs on a schedule.

References

Originally published on Hashnode.

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

How Lighthouse Performance Scores Are Recorded and Calculated

ApogeeWatcherverified - Aug 18

Is PageSpeed Insights a good reference for web page structure/performance?

ApogeeWatcherverified - Aug 28

Getting Started with Apogee Watcher: A Step-by-Step Setup Guide

ApogeeWatcherverified - Jul 5

Lab green, field amber: reconciling two PageSpeed truths

ApogeeWatcherverified - Jul 19

Why is my Chrome UX Report much worse than Lighthouse? How can I improve it?

ApogeeWatcherverified - Aug 22
chevron_left
7.8k Points182 Badges
147Posts
33Comments
84Connections
We bring quality & creative intelligence since 2002. We design, develop and operate custom informati... Show more

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!