|
1 | 1 | import fs from 'fs'; |
2 | 2 | import path from 'path'; |
| 3 | +import crypto from 'crypto'; |
3 | 4 | import sharp from 'sharp'; |
4 | 5 | import { NextRequest, NextResponse } from 'next/server'; |
5 | 6 |
|
@@ -66,9 +67,16 @@ export async function GET(request: NextRequest) { |
66 | 67 | return NextResponse.json({ error: 'File not found' }, { status: 404 }); |
67 | 68 | } |
68 | 69 |
|
69 | | - // Stat the file for ETag / Last-Modified |
| 70 | + // Stat the file and build a cache key that is unique per source + transform params. |
| 71 | + // This prevents different image URLs from sharing the same cache validator. |
70 | 72 | const stat = fs.statSync(filePath); |
71 | | - const etag = `"${stat.size}-${stat.mtimeMs}"`; |
| 73 | + const cacheKey = [ |
| 74 | + filePath, |
| 75 | + stat.size, |
| 76 | + stat.mtimeMs, |
| 77 | + searchParams.toString(), |
| 78 | + ].join('|'); |
| 79 | + const etag = `"${crypto.createHash('sha1').update(cacheKey).digest('hex')}"`; |
72 | 80 | const lastModified = stat.mtime.toUTCString(); |
73 | 81 |
|
74 | 82 | // Conditional request support |
@@ -149,7 +157,12 @@ export async function GET(request: NextRequest) { |
149 | 157 | const outputBuffer = await pipeline.toBuffer(); |
150 | 158 | const contentType = MIME[format] ?? 'image/webp'; |
151 | 159 |
|
152 | | - return new NextResponse(outputBuffer.buffer as ArrayBuffer, { |
| 160 | + const responseBody = outputBuffer.buffer.slice( |
| 161 | + outputBuffer.byteOffset, |
| 162 | + outputBuffer.byteOffset + outputBuffer.byteLength, |
| 163 | + ) as ArrayBuffer; |
| 164 | + |
| 165 | + return new NextResponse(responseBody, { |
153 | 166 | status: 200, |
154 | 167 | headers: { |
155 | 168 | 'Content-Type': contentType, |
|
0 commit comments