diff --git a/apps/web/src/cloudflare/static-assets.test.ts b/apps/web/src/cloudflare/static-assets.test.ts index 1380115463..238a6d25c8 100644 --- a/apps/web/src/cloudflare/static-assets.test.ts +++ b/apps/web/src/cloudflare/static-assets.test.ts @@ -92,6 +92,19 @@ test("redirects moved objects to their new path without fetching the old one", a response.headers.get("location"), "https://static.anarlog.so/blog/library/products/otter/meeting-summary/2026-08-27-summary-card.webp", ); + const encoded = await worker.fetch( + new Request( + "https://static.anarlog.so/blog/articles/free-ai-notetakers/free%2D11.webp", + ), + ); + assert.equal(encoded.status, 301); + const preflight = await worker.fetch( + new Request( + "https://static.anarlog.so/blog/articles/free-ai-notetakers/free-11.webp", + { method: "OPTIONS" }, + ), + ); + assert.equal(preflight.status, 204); const inherited = await worker.fetch( new Request("https://static.anarlog.so/constructor"), ); diff --git a/apps/web/src/cloudflare/static-assets.ts b/apps/web/src/cloudflare/static-assets.ts index 15f8e26441..58198c1652 100644 --- a/apps/web/src/cloudflare/static-assets.ts +++ b/apps/web/src/cloudflare/static-assets.ts @@ -1,6 +1,17 @@ import { servePublicAsset } from "../lib/public-assets.ts"; import { MOVED_ASSETS } from "./moved-assets.ts"; +// Matches how servePublicAsset reads paths, so encoded spellings of an old path redirect too. +function movedAsset(pathname: string) { + let path: string; + try { + path = decodeURIComponent(pathname.slice(1)); + } catch { + return undefined; + } + return Object.hasOwn(MOVED_ASSETS, path) ? MOVED_ASSETS[path] : undefined; +} + export default { async fetch(request: Request): Promise { const url = new URL(request.url); @@ -11,10 +22,9 @@ export default { url.protocol = "https:"; return Response.redirect(url, 308); } - const moved = Object.hasOwn(MOVED_ASSETS, url.pathname.slice(1)) - ? MOVED_ASSETS[url.pathname.slice(1)] - : undefined; - if (moved) { + const moved = movedAsset(url.pathname); + // Preflights are answered below: browsers do not follow a redirected preflight. + if (moved && (request.method === "GET" || request.method === "HEAD")) { return new Response(null, { status: 301, headers: {