A performance case study in the shape of a repo. It is one landing page for a fictional web performance monitoring product, built twice: an optimized version and a deliberately slow baseline. Both are deployed, and both are measured with Lighthouse against the live URL. The interesting part is not that the optimized page is fast, it is why each decision makes it fast, explained so it holds up in an interview.
- Live (optimized): https://featherweight.filipespan.workers.dev
- Live (baseline): https://featherweight.filipespan.workers.dev/baseline/
- Português: https://featherweight.filipespan.workers.dev/pt/
- Stack: Astro 7 (zero JavaScript shipped), Cloudflare Workers static assets
The page ships in English and Portuguese. Each language is its own static route
(/ and /pt/), not a client-side toggle, so the site keeps shipping zero
JavaScript and a shared link keeps the language it was read in. The Lighthouse
numbers below are measured against the English routes.
Built by Filipe Spanghero.
Same content, same layout, same design. The only thing that changes is how the page is delivered. The baseline commits the mistakes you see on real sites: fonts loaded from Google at runtime, a render blocking stylesheet, a blocking script in the head, and full size PNGs with no dimensions and no lazy loading. The optimized page fixes each one.
Numbers below come from Lighthouse 12 run against the live URLs. Mobile is the Lighthouse default (a throttled mid tier phone on slow 4G), which is where delivery mistakes actually hurt. You can reproduce them from Chrome DevTools or PageSpeed Insights.
| Baseline | Optimized | |
|---|---|---|
| Performance | 75 | 98 |
| Accessibility | 100 | 100 |
| Best Practices | 100 | 100 |
| SEO | 66¹ | 100 |
| Largest Contentful Paint | 4.6 s | 2.0 s |
| Cumulative Layout Shift | 0 | 0 |
| Total transfer | 378 KB | 129 KB |
| Baseline | Optimized | |
|---|---|---|
| Performance | 95 | 99 |
| Largest Contentful Paint | 1.2 s | 0.7 s |
| Total transfer | 378 KB | 135 KB |
The extracted scores and metrics are committed in
lighthouse-live/summary.json, so the numbers
are not just claims in a table. Regenerate them any time with
npm run build && npm run deploy followed by a Lighthouse run against the live
URL.
¹ The baseline scores 66 on SEO because it is marked noindex. That is on
purpose: it is a measurement artifact, not a page I want in search results. It is
the one number where lower is correct.
- Cumulative Layout Shift came out 0 on both pages. The optimized page sets
explicit
width/heighton every image to reserve space, which is the right thing to do. On this particular layout the baseline did not shift enough to register either, so I am not going to claim a CLS win I did not measure. The win that did show up is Largest Contentful Paint and total bytes. - Optimized mobile is 98, not 100. The two missing points are First Contentful Paint and LCP under simulated slow 4G, where the hero image is bandwidth bound. The only Lighthouse opportunity left is about 85 ms of edge response time. There is no avoidable mistake to fix, so I would rather report 98 than tune the test until it says 100.
Each of these is a lever the baseline pulls the wrong way.
The baseline links fonts.googleapis.com in the head. That is a render blocking
request to a third party, a second connection to open, and a font that arrives
after first paint (the flash of unstyled text).
The optimized page uses Astro's fonts API to download Space Grotesk and Inter at
build time, subset them to the Latin range, and self host the woff2 files
from the same origin. It preloads the two families used above the fold and lets
Astro generate size adjusted fallback faces so the swap does not move layout.
Result: no third party connection, no runtime dependency on Google, and the font
is on the critical path from the first byte. This is also the more robust build:
nothing is fetched from a network the deploy does not control.
The baseline ships an external styles.css the browser must fetch and parse
before it can paint. The optimized page inlines all of its CSS into the document
(build.inlineStylesheets: 'always'), so the HTML arrives paint ready in a
single request. The whole document is about 9 KB gzipped.
The three product mockups start as the same source PNGs. The baseline links them
full size (up to 2560 px wide) with no width/height, no srcset, and no lazy
loading. The optimized page runs them through Astro's <Picture>, which emits
AVIF and WebP at several widths with sizes, sets explicit dimensions, marks the
hero as fetchpriority="high", and lazy loads everything below the fold. The
hero dashboard goes from a 112 KB PNG to a 9 KB AVIF at display size.
The baseline loads a script in the head with no defer, so the browser stops
parsing to download and run it. The optimized page ships no JavaScript at
all. The mobile menu is a native <details> element, the FAQ is a set of
<details> accordions, and light and dark themes come from
prefers-color-scheme. Nothing hydrates because there is nothing to hydrate.
Semantic landmarks, a skip link, labelled controls, alt text on every image,
visible keyboard focus, and prefers-reduced-motion honored on the one
animation (the latency marker). Accessibility scores 100 on both pages, and
axe-core runs the full WCAG 2 A and AA rule set against both in CI.
The checks are automated so a regression fails the build rather than shipping.
| Check | Tool | What it guards |
|---|---|---|
| Types | astro check (strict) |
No any, no type errors |
| Performance and SEO budgets | @lhci/cli |
Fails if Performance drops below 95 or Accessibility/SEO below 100, plus specific audits for modern image formats, responsive images, no render blocking resources, and font-display |
| Accessibility | @axe-core/playwright |
Zero WCAG 2 A/AA violations on both pages |
| Smoke | Playwright | Page renders, hero image decodes, no failed requests, no render blocking script or external CSS, fonts are same origin |
All of it runs on every push and pull request through GitHub Actions.
npm run typecheck # astro check
npm run build # static build to dist/
npm run test:a11y # axe + smoke via Playwright
npm run lhci # Lighthouse budgets against the built siteThe Lighthouse config blocks
*kaspersky-labs.com*, because some dev machines run an antivirus that injects its own scripts into local HTTP pages. That is not the page's code, so it is filtered out to measure what actually ships. In CI it is a no op.
- Astro 7, static output. No adapter, no server, no client JavaScript.
- Fonts self hosted via the built in fonts API (
fontProviders.google()). - Images optimized by Astro's Sharp service into AVIF and WebP.
- Product mockups are generated from SVG by
scripts/gen-assets.mjsand rasterized with Sharp, so the same source pixels feed both the optimized and baseline pages. The comparison measures delivery, not different artwork. - Deploy to Cloudflare Workers static assets with
wrangler.
src/
components/ UI, one concern each (Hero, Features, Showcase, Faq, ...)
data/site.ts all page copy, typed and shared by both pages
layouts/ the document shell, meta and font preloads
pages/
index.astro the optimized landing
baseline/index.astro the deliberately slow version
styles/global.css design tokens and base styles
public/baseline/ the baseline's external CSS, blocking JS and raw images
scripts/ asset generation and screenshot helpers
npm install
npm run assets # regenerate the product mockups (optional, they are committed)
npm run dev # localhost:4321npm run deploy # astro build && wrangler deployMIT. See LICENSE.
Part of a small set of public projects. See also cable-ui (React component library) and quire (headless CMS), and the rest at filipe.span.dev.br.

