perf(images): WebP banners, text-only related cards, resized list thumbnails
Deploy / build (push) Successful in 56s
Deploy / build (push) Successful in 56s
Measured before/after with headless Chrome + CDP (not by eye):
- article banner: <picture> WebP q78 with the PNG as fallback, because the PNG
must stay for og:image (social scrapers do not reliably render WebP cards).
70 banners: 6.02 MB -> 2.52 MB (42%). JPEG measured *larger* than PNG here
(q85 = 129%) — the art is flat terminal-style with sharp text edges.
- post page related cards: dropped four 1200x630 PNGs (183 KB) to display
526x276 where the embedded code is illegible; now text-only cards with a
category badge, date and reading time.
- list thumbnails (home + archive): new /og/thumb/<name>-{400,800}.webp served
via srcset/sizes. Rows display at ~398px but were pulling full 1200x630 PNGs.
Homepage thumbnails ~450 KB -> 62 KB; archive (71 rows) ~3.2 MB -> 436 KB.
- desktop banner capped at 720px centred: 67% -> 45% of a 900px viewport,
keeping the art intact instead of cropping it.
Post page totals: images 277 KB -> 38 KB; page height 5758 -> 5065 (desktop)
and 9423 -> 8807 (mobile). Build: 163 pages.
Script: scripts/optimize-images.py; policy: docs/design-guide.md §3; runbook
step added to docs/ops-runbook.md.
This commit is contained in:
1 parent
9462ab2373
commit
f364084284
219 files changed
+158
-15
No files matched your search
@@ -23,13 +23,32 @@ const updatedLabel = isZh ? '更新于' : 'Updated';
|
||||
const catHref = (cat: string) => `/${isZh ? 'zh/categories' : 'categories'}/${cat}/`;
|
||||
|
||||
const og = (post: CollectionEntry<'posts'>) => post.data.ogImage || '/og-default.png';
|
||||
|
||||
// The row thumbnail is displayed at ~398 CSS px wide, so the list used to pull a
|
||||
// 1200x630 PNG (~45 KB) per row for a 398px slot. Serve 400w/800w WebP derived from
|
||||
// the same art instead (5.8 KB / 16.1 KB — see scripts/optimize-images.py).
|
||||
const hasThumb = (post: CollectionEntry<'posts'>) => {
|
||||
const p = og(post);
|
||||
return p.startsWith('/og/') && p !== '/og-default.png';
|
||||
};
|
||||
const thumb = (post: CollectionEntry<'posts'>, w: 400 | 800) =>
|
||||
og(post).replace('/og/', '/og/thumb/').replace(/\.png$/, `-${w}.webp`);
|
||||
---
|
||||
|
||||
<ul class="post-list">
|
||||
{shown.map((post) => (
|
||||
<li class="post-row">
|
||||
<a class="thumb" href={`/posts/${post.slug}/`} aria-hidden="true" tabindex="-1">
|
||||
<img src={og(post)} alt="" loading="lazy" width="1200" height="630" />
|
||||
<img
|
||||
src={hasThumb(post) ? thumb(post, 800) : og(post)}
|
||||
srcset={hasThumb(post) ? `${thumb(post, 400)} 400w, ${thumb(post, 800)} 800w` : undefined}
|
||||
sizes="(max-width: 640px) 100vw, 398px"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width={hasThumb(post) ? 800 : 1200}
|
||||
height={hasThumb(post) ? 420 : 630}
|
||||
/>
|
||||
</a>
|
||||
<div class="body">
|
||||
<a href={`/posts/${post.slug}/`}><h2>{post.data.title}</h2></a>
|
||||
|
||||
Reference in new issue
Block a user