From e1e4c205e1a32bf0b097db1bfd9392f066341610 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 29 Apr 2026 16:48:30 +0800 Subject: [PATCH] fix: bypass image proxy for browser image loads --- app/api/image-proxy/route.ts | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/app/api/image-proxy/route.ts b/app/api/image-proxy/route.ts index 19e041faf..faf9ac887 100644 --- a/app/api/image-proxy/route.ts +++ b/app/api/image-proxy/route.ts @@ -90,6 +90,80 @@ function buildEtag(input: string): string { return `"${crypto.createHash("sha1").update(input).digest("hex")}"`; } +function isBrowserImageRequest(request: NextRequest): boolean { + return request.headers.get("sec-fetch-dest") === "image"; +} + +function isAliyunOssHost(hostname: string): boolean { + const normalized = hostname.toLowerCase(); + return normalized.endsWith(".aliyuncs.com") && normalized.includes(".oss-"); +} + +function appendAliyunImageProcess( + target: URL, + width: number | null, + quality: number | null, + requestedFormat: string | null +): URL | null { + if (target.searchParams.has("x-oss-process")) { + return width || quality || requestedFormat ? null : target; + } + + const processSteps: string[] = ["image"]; + if (width) { + processSteps.push(`resize,w_${width}`); + } + + if (quality) { + processSteps.push(`quality,q_${quality}`); + } + + if (requestedFormat) { + const format = requestedFormat === "jpeg" ? "jpg" : requestedFormat; + if (format !== "webp" && format !== "jpg" && format !== "png") { + return null; + } + processSteps.push(`format,${format}`); + } + + const redirected = new URL(target.toString()); + redirected.searchParams.set("x-oss-process", processSteps.join("/")); + return redirected; +} + +function resolveDirectImageTarget( + request: NextRequest, + target: URL, + width: number | null, + requestedQuality: number | null, + requestedFormat: string | null +): URL | null { + if (!isBrowserImageRequest(request)) { + return null; + } + + const hasTransformRequest = Boolean(width || requestedQuality || requestedFormat); + if (!hasTransformRequest) { + return target; + } + + if (isAliyunOssHost(target.hostname)) { + return appendAliyunImageProcess(target, width, requestedQuality, requestedFormat); + } + + return null; +} + +function redirectToDirectImage(target: URL): NextResponse { + const response = NextResponse.redirect(target, 307); + response.headers.set( + "Cache-Control", + "public, max-age=31536000, immutable, stale-while-revalidate=86400" + ); + response.headers.set("X-Image-Proxy-Bypass", "direct-image-request"); + return response; +} + export async function GET(request: NextRequest) { const { searchParams } = request.nextUrl; const rawUrl = (searchParams.get("url") || "").trim(); @@ -116,8 +190,20 @@ export async function GET(request: NextRequest) { const requestedQuality = parsePositiveInt(searchParams.get("q")); const width = requestedWidth ? clamp(requestedWidth, MIN_WIDTH, MAX_WIDTH) : null; const quality = requestedQuality ? clamp(requestedQuality, MIN_QUALITY, MAX_QUALITY) : DEFAULT_QUALITY; + const directQuality = requestedQuality ? clamp(requestedQuality, MIN_QUALITY, MAX_QUALITY) : null; const requestedFormat = searchParams.get("f"); + const directImageTarget = resolveDirectImageTarget( + request, + target, + width, + directQuality, + requestedFormat + ); + if (directImageTarget) { + return redirectToDirectImage(directImageTarget); + } + const cacheKey = `${target.toString()}|${width || "auto"}|${quality}|${requestedFormat || "auto"}`; const etag = buildEtag(cacheKey); const ifNoneMatch = request.headers.get("if-none-match");