open-design/apps/landing-page/app/_components/lazy-img.astro
lefarcen 7fc4362ba8
perf(landing): edge-cache HTML and precise-load thumbnails (#2235)
* perf(landing): edge-cache HTML and precise-load thumbnails

Without `public/_headers` Cloudflare Pages serves every HTML with
`cf-cache-status: DYNAMIC` so each request roundtrips to the Pages
origin — observed TTFB 660–900ms from Seattle, worse from Asia.
With `s-maxage=3600, stale-while-revalidate=86400` HTML stays cached
at the edge between deploys (CF Pages auto-purges on every deploy so
freshness is unchanged in practice), and `_astro/` hash bundles flip
to `immutable` so the existing 4h+must-revalidate roundtrips go away.

For thumbnails, native `loading="lazy"` is browser-decided —
Chrome over-prefetches (1250–3000px), Safari fires near in-viewport.
A new `<LazyImg>` Astro component and global IntersectionObserver
(rootMargin 300px for images, 600px for videos) replaces all 10
site-wide `loading="lazy"` usages with precise control. Above-the-fold
slots (first 4 rows, detail-page hero previews) opt into `eager` or
`priority` to skip the IO roundtrip.

Homepage hero LCP gets `<link rel="preload" imagesrcset>`, a 4-step
`srcset` (768/1280/1920/2560) plus `fetchpriority="high"` so retina
devices stop repainting from the 1024-only variant — was the P99 long
tail.

Verified: `pnpm guard` 6/6, `pnpm typecheck` 0 errors, `pnpm build`
865 pages 28s, generated `out/index.html` contains the preload link
and 15 `data-precise-src` thumbnails, `out/plugins/index.html` has
95 precise-loaded thumbnails plus the IO script.

* perf(landing): logo to webp + parallelize Google Fonts load

Two HAR-validated wins on top of the edge-cache / precise-load commit:

logo: 500x500 192KB PNG → 200x200 7.5KB WebP. Footer/header actually
render at 36x36, so the source is 5x larger than necessary at the
display size and ships RGBA PNG bytes for what reads as a flat
graphic. WebP at q=85 keeps the gradient ring crisp at every DPR we
care about.

fonts: globals.css used `@import url(...)` for Google Fonts, which
serialized HTML → CSS → fonts.googleapis.com/css2 → fonts.gstatic.com/
woff2. HAR measured 953ms for the fonts CSS plus 400–800ms per woff2
× 4 — close to 3s before text could render in the intended family,
even with display=swap. Moving to `<link>` + `<link rel=preconnect>`
in each page's <head> lets the fonts CSS fetch race the HTML body
parse, and warms the TLS handshake to gstatic.com so woff2 requests
don't pay DNS+TLS at request time.

A shared `font-stylesheet.astro` keeps the four-family URL canonical
across all five entry points (index, sub-page-layout, plugins/index,
plugins/[slug], blog/index, blog/[slug]). og.astro already had this
treatment.
2026-05-19 19:14:25 +08:00

96 lines
2.6 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
/*
* <LazyImg> — the only image primitive used for non-LCP thumbnails on this
* site. Replaces every previous `<img loading="lazy">` site-wide.
*
* Modes:
* - default (precise): outputs `<img data-precise-src>` with a 120-byte
* transparent SVG as the initial `src`. The global IntersectionObserver
* in `precise-lazyload.astro` swaps `data-precise-src` → `src` once the
* element enters viewport ± 300px. Use this for *every* thumbnail
* below the fold. Net effect vs. `loading="lazy"`: Chrome stops
* over-prefetching (default rootMargin 12503000px), Safari stops
* under-prefetching (essentially in-viewport), and CLS is unaffected
* because callers wrap us in an aspect-ratio container.
*
* - priority: outputs `<img src loading="eager" fetchpriority="high">`
* for the rare thumbnail that's known to be above the fold AND on the
* LCP candidate path. Use sparingly — the hero image on `/` is the
* canonical case (and is hand-written, not via this component).
*
* - eager (default for above-the-fold non-LCP): outputs `<img src>` with
* no lazy behavior. Faster than precise mode for the first row of a
* visible grid; doesn't compete with the LCP for fetchpriority.
*
* Why a component and not a helper function:
* `srcset` + `sizes` are awkward to inline in JSX/Astro; a component
* gives us one place to type-check and apply width/height (for CLS).
*/
import { PRECISE_LAZY_PLACEHOLDER } from '../image-assets';
export interface Props {
src: string;
alt: string;
/**
* 'precise' (default): wait until viewport ± 300px.
* 'eager': load immediately, normal priority.
* 'priority': load immediately, fetchpriority="high".
*/
loading?: 'precise' | 'eager' | 'priority';
srcset?: string;
sizes?: string;
width?: number | string;
height?: number | string;
class?: string;
}
const {
src,
alt,
loading = 'precise',
srcset,
sizes,
width,
height,
class: className,
} = Astro.props;
---
{loading === 'precise' ? (
<img
src={PRECISE_LAZY_PLACEHOLDER}
data-precise-src={src}
data-precise-srcset={srcset}
sizes={sizes}
alt={alt}
width={width}
height={height}
class={className}
decoding="async"
/>
) : loading === 'priority' ? (
<img
src={src}
srcset={srcset}
sizes={sizes}
alt={alt}
width={width}
height={height}
class={className}
loading="eager"
fetchpriority="high"
decoding="async"
/>
) : (
<img
src={src}
srcset={srcset}
sizes={sizes}
alt={alt}
width={width}
height={height}
class={className}
loading="eager"
decoding="async"
/>
)}