perf: add ETag and server-side caching for thumbnail proxy
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 49s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 49s
Add ETag header to API thumbnail responses for 304 Not Modified support. Forward If-None-Match/ETag through the Next.js proxy route handler and add next.revalidate for 24h server-side fetch caching to reduce SSR-to-API round trips on the libraries page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -631,12 +631,17 @@ pub async fn get_thumbnail(
|
||||
crate::pages::render_book_page_1(&state, book_id, 300, 80).await?
|
||||
};
|
||||
|
||||
let etag_value = format!("\"{}_{:x}\"", book_id, data.len());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
|
||||
headers.insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=31536000, immutable"),
|
||||
);
|
||||
if let Ok(v) = HeaderValue::from_str(&etag_value) {
|
||||
headers.insert(header::ETAG, v);
|
||||
}
|
||||
|
||||
Ok((StatusCode::OK, headers, Body::from(data)))
|
||||
}
|
||||
|
||||
@@ -9,10 +9,25 @@ export async function GET(
|
||||
|
||||
try {
|
||||
const { baseUrl, token } = config();
|
||||
const ifNoneMatch = request.headers.get("if-none-match");
|
||||
|
||||
const fetchHeaders: Record<string, string> = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
if (ifNoneMatch) {
|
||||
fetchHeaders["If-None-Match"] = ifNoneMatch;
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}/books/${bookId}/thumbnail`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
headers: fetchHeaders,
|
||||
next: { revalidate: 86400 },
|
||||
});
|
||||
|
||||
// Forward 304 Not Modified as-is
|
||||
if (response.status === 304) {
|
||||
return new NextResponse(null, { status: 304 });
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return new NextResponse(`Failed to fetch thumbnail: ${response.status}`, {
|
||||
status: response.status
|
||||
@@ -20,13 +35,17 @@ export async function GET(
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type") || "image/webp";
|
||||
const etag = response.headers.get("etag");
|
||||
|
||||
return new NextResponse(response.body, {
|
||||
headers: {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": contentType,
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
},
|
||||
});
|
||||
};
|
||||
if (etag) {
|
||||
headers["ETag"] = etag;
|
||||
}
|
||||
|
||||
return new NextResponse(response.body, { headers });
|
||||
} catch (error) {
|
||||
console.error("Error fetching thumbnail:", error);
|
||||
return new NextResponse("Failed to fetch thumbnail", { status: 500 });
|
||||
|
||||
Reference in New Issue
Block a user