From 08ad51152f9573b37acfaad6d4b3394de8fd4f51 Mon Sep 17 00:00:00 2001
From: parsa aghaei
Date: Wed, 2 Sep 2026 16:14:33 +0330
Subject: [PATCH] feat(images): enable Next.js image optimization with complete
upstream buffer retrieval and explicit MIME types
---
.../app/api/media/[...path]/route.ts | 43 ++++++++++++++++++-
.../app/api/uploads/[...path]/route.ts | 40 ++++++++++++++++-
frontend/application/components/Hero.tsx | 2 -
frontend/application/components/SafeImage.tsx | 7 +--
4 files changed, 81 insertions(+), 11 deletions(-)
diff --git a/frontend/application/app/api/media/[...path]/route.ts b/frontend/application/app/api/media/[...path]/route.ts
index e92d452..1f84af8 100644
--- a/frontend/application/app/api/media/[...path]/route.ts
+++ b/frontend/application/app/api/media/[...path]/route.ts
@@ -32,6 +32,24 @@ function getCandidateUrls(filePath: string): string[] {
return Array.from(set);
}
+function getMimeType(filePath: string, fallback: string): string {
+ const ext = filePath.split('.').pop()?.toLowerCase();
+ switch (ext) {
+ case 'webp': return 'image/webp';
+ case 'jpg':
+ case 'jpeg': return 'image/jpeg';
+ case 'png': return 'image/png';
+ case 'gif': return 'image/gif';
+ case 'svg': return 'image/svg+xml';
+ case 'avif': return 'image/avif';
+ case 'mp4': return 'video/mp4';
+ case 'webm': return 'video/webm';
+ case 'mp3': return 'audio/mpeg';
+ case 'pdf': return 'application/pdf';
+ default: return fallback || 'application/octet-stream';
+ }
+}
+
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
@@ -42,9 +60,11 @@ export async function GET(
return new NextResponse('Bad Request: Missing file path', { status: 400 });
}
+ const rawFilePath = path.join('/');
const filePath = path.map(segment => encodeURIComponent(segment)).join('/');
const candidates = getCandidateUrls(filePath);
+ // Forward streaming/caching headers from client
const forwardedHeaders: HeadersInit = {};
const rangeHeader = request.headers.get('range');
if (rangeHeader) forwardedHeaders['range'] = rangeHeader;
@@ -68,7 +88,25 @@ export async function GET(
break;
}
} catch {
- // Continue
+ // Continue trying next candidate
+ }
+ }
+
+ // If 304 received without body or if not found with conditional headers, retry unconditionally
+ if (backendRes && backendRes.status === 304 && !backendRes.body) {
+ for (const targetUrl of candidates) {
+ try {
+ const freshRes = await fetch(targetUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+ if (freshRes.ok) {
+ backendRes = freshRes;
+ break;
+ }
+ } catch {
+ // Continue
+ }
}
}
@@ -77,7 +115,8 @@ export async function GET(
}
const responseHeaders = new Headers();
- const contentType = backendRes.headers.get('content-type') || 'application/octet-stream';
+ const rawContentType = backendRes.headers.get('content-type') || '';
+ const contentType = getMimeType(rawFilePath, rawContentType);
responseHeaders.set('Content-Type', contentType);
const contentLength = backendRes.headers.get('content-length');
diff --git a/frontend/application/app/api/uploads/[...path]/route.ts b/frontend/application/app/api/uploads/[...path]/route.ts
index d2f738c..49e64da 100644
--- a/frontend/application/app/api/uploads/[...path]/route.ts
+++ b/frontend/application/app/api/uploads/[...path]/route.ts
@@ -34,6 +34,24 @@ function getCandidateUrls(filePath: string): string[] {
return Array.from(set);
}
+function getMimeType(filePath: string, fallback: string): string {
+ const ext = filePath.split('.').pop()?.toLowerCase();
+ switch (ext) {
+ case 'webp': return 'image/webp';
+ case 'jpg':
+ case 'jpeg': return 'image/jpeg';
+ case 'png': return 'image/png';
+ case 'gif': return 'image/gif';
+ case 'svg': return 'image/svg+xml';
+ case 'avif': return 'image/avif';
+ case 'mp4': return 'video/mp4';
+ case 'webm': return 'video/webm';
+ case 'mp3': return 'audio/mpeg';
+ case 'pdf': return 'application/pdf';
+ default: return fallback || 'application/octet-stream';
+ }
+}
+
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
@@ -44,6 +62,7 @@ export async function GET(
return new NextResponse('Bad Request: Missing file path', { status: 400 });
}
+ const rawFilePath = path.join('/');
const filePath = path.map(segment => encodeURIComponent(segment)).join('/');
const candidates = getCandidateUrls(filePath);
@@ -75,12 +94,31 @@ export async function GET(
}
}
+ // If 304 received without body or if not found with conditional headers, retry unconditionally
+ if (backendRes && backendRes.status === 304 && !backendRes.body) {
+ for (const targetUrl of candidates) {
+ try {
+ const freshRes = await fetch(targetUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+ if (freshRes.ok) {
+ backendRes = freshRes;
+ break;
+ }
+ } catch {
+ // Continue
+ }
+ }
+ }
+
if (!backendRes) {
return new NextResponse('Media file not found upstream', { status: 404 });
}
const responseHeaders = new Headers();
- const contentType = backendRes.headers.get('content-type') || 'application/octet-stream';
+ const rawContentType = backendRes.headers.get('content-type') || '';
+ const contentType = getMimeType(rawFilePath, rawContentType);
responseHeaders.set('Content-Type', contentType);
const contentLength = backendRes.headers.get('content-length');
diff --git a/frontend/application/components/Hero.tsx b/frontend/application/components/Hero.tsx
index ddf36fe..9fa76ce 100644
--- a/frontend/application/components/Hero.tsx
+++ b/frontend/application/components/Hero.tsx
@@ -198,7 +198,6 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
alt={banner.title || "Canina Pharma Germany"}
fill
priority={idx === 0}
- unoptimized={true}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 600px"
className="object-cover"
/>
@@ -211,7 +210,6 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
alt={activeBanner?.title || "Canina Pharma Germany"}
fill
priority
- unoptimized={true}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 600px"
className="object-cover"
/>
diff --git a/frontend/application/components/SafeImage.tsx b/frontend/application/components/SafeImage.tsx
index 7686768..733da6f 100644
--- a/frontend/application/components/SafeImage.tsx
+++ b/frontend/application/components/SafeImage.tsx
@@ -104,12 +104,7 @@ export default function SafeImage({
unoptimized={
isBlobOrData ||
!isAllowedHost ||
- (typeof src === 'string' && (
- src.startsWith('/api/uploads/') ||
- src.startsWith('/api/media/') ||
- src.startsWith('/uploads/') ||
- src.toLowerCase().endsWith('.svg')
- ))
+ (typeof src === 'string' && src.toLowerCase().endsWith('.svg'))
}
className={cn(
"transition-all duration-500 w-full h-full object-contain",