Skip to content

Commit c2d72d1

Browse files
perf(app): use _400.webp variants for map thumbnails
Map nodes render at ~22 px but were loading the full-size GCS PNGs (~40–100 KB each) — total ~15 MB for 312 specs. The responsive-image pipeline already bakes _400.webp variants (~6 KB each), so swap selectMapThumbUrl to derive `{base}_400.webp` from the chosen theme URL. Total payload drops to ~2 MB. Falls back to the original URL if it isn't `.png`. Refs #5646 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e88ad99 commit c2d72d1

2 files changed

Lines changed: 28 additions & 7 deletions

File tree

app/src/pages/MapPage.helpers.test.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,28 @@ describe('buildKNNLinks', () => {
160160

161161

162162
describe('selectMapThumbUrl', () => {
163-
it('returns dark URL in dark mode, light in light mode', () => {
163+
it('returns the _400.webp variant for the current theme', () => {
164164
const s = spec('a', null);
165-
expect(selectMapThumbUrl(s, true)).toBe('https://example.com/a-dark.png');
166-
expect(selectMapThumbUrl(s, false)).toBe('https://example.com/a-light.png');
165+
expect(selectMapThumbUrl(s, true)).toBe('https://example.com/a-dark_400.webp');
166+
expect(selectMapThumbUrl(s, false)).toBe('https://example.com/a-light_400.webp');
167167
});
168168

169-
it('falls back to the other theme when the preferred URL is missing', () => {
169+
it('falls back to the other theme variant when the preferred URL is missing', () => {
170170
const s: SpecMapItem = { ...spec('a', null), preview_url_dark: null };
171-
expect(selectMapThumbUrl(s, true)).toBe('https://example.com/a-light.png');
171+
expect(selectMapThumbUrl(s, true)).toBe('https://example.com/a-light_400.webp');
172172
});
173173

174174
it('returns null when no preview URLs at all', () => {
175175
const s: SpecMapItem = { ...spec('a', null), preview_url_light: null, preview_url_dark: null };
176176
expect(selectMapThumbUrl(s, false)).toBeNull();
177177
});
178+
179+
it('returns the original URL unchanged if it does not end in .png', () => {
180+
const s: SpecMapItem = {
181+
...spec('a', null),
182+
preview_url_light: 'https://example.com/a-light.svg',
183+
preview_url_dark: null,
184+
};
185+
expect(selectMapThumbUrl(s, false)).toBe('https://example.com/a-light.svg');
186+
});
178187
});

app/src/pages/MapPage.helpers.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,21 @@ export function buildKNNLinks(
142142
return Array.from(linkSet.values());
143143
}
144144

145-
/** Pick the best thumbnail URL for the current theme. Wraps selectPreviewUrl. */
145+
/**
146+
* Pick the best thumbnail URL for the current theme and downsize it to the
147+
* `_400.webp` variant produced by the responsive-image pipeline. Map nodes
148+
* render at ~22 px, so even 400 px is overkill — but 400 is the smallest
149+
* pipeline-baked variant. Going from full-size (~40–100 KB) to _400.webp
150+
* (~6 KB) cuts the 312-thumbnail payload from ~15 MB to ~2 MB.
151+
*
152+
* Falls back to the original full-size URL if the convention can't be
153+
* applied (e.g. URL doesn't end in `.png`).
154+
*/
146155
export function selectMapThumbUrl(spec: SpecMapItem, isDark: boolean): string | null {
147-
return selectPreviewUrl(spec, isDark);
156+
const full = selectPreviewUrl(spec, isDark);
157+
if (!full) return null;
158+
if (!full.endsWith('.png')) return full;
159+
return full.replace(/\.png$/, '_400.webp');
148160
}
149161

150162
/**

0 commit comments

Comments
 (0)