perf(web-player): serve YouTube artwork through a caching proxy

The browser used to pull every cover straight from Google, so a search grid
plus infinite scroll fired dozens of cross-origin image requests and started
coming back as 429. Covers now go through /api/youtube/thumbnail, which
resizes the upstream URL, caches the bytes in memory, collapses duplicate
in-flight fetches, caps the concurrency against Google and backs off when it
answers 429. Responses are immutable for a month and revalidate with an ETag,
and serving them same-origin also lets the accent extractor read the pixels.
This commit is contained in:
2026-07-20 17:36:41 +02:00
parent dd3150e3d4
commit 2691c40da8
6 changed files with 262 additions and 3 deletions
@@ -0,0 +1,31 @@
import type { RequestHandler } from './$types';
import { error } from '@sveltejs/kit';
import { loadThumbnail, parseThumbnailUrl } from '$lib/server/thumbnails';
import { thumbnailConfig } from '$lib/server/config';
import { THUMBNAIL_SIZES } from '$lib/thumbnails';
export const GET: RequestHandler = async ({ url, request, setHeaders }) => {
const source = parseThumbnailUrl(url.searchParams.get('u') ?? '');
if (!source) error(400, 'Miniatura no permitida.');
const requested = Number(url.searchParams.get('size'));
const size =
Number.isInteger(requested) && requested > 0
? Math.min(requested, thumbnailConfig.maxSize)
: THUMBNAIL_SIZES.large;
const image = await loadThumbnail(source, size);
if (!image) error(404, 'Miniatura no disponible.');
setHeaders({
'Cache-Control': `public, max-age=${thumbnailConfig.maxAgeSeconds}, immutable`,
'Content-Type': image.contentType,
ETag: image.etag
});
if (request.headers.get('if-none-match') === image.etag) {
return new Response(null, { status: 304 });
}
return new Response(image.body);
};