Skip to content

Commit 6d36c0a

Browse files
committed
refactor: improve error handling for image optimization to distinguish between client and server errors
1 parent dcfe64b commit 6d36c0a

File tree

1 file changed

+86
-26
lines changed

1 file changed

+86
-26
lines changed

packages/open-next/src/adapters/image-optimization-adapter.ts

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { emptyReadableStream, toReadableStream } from "utils/stream.js";
2828
import type { OpenNextHandlerOptions } from "types/overrides.js";
2929
import { createGenericHandler } from "../core/createGenericHandler.js";
3030
import { resolveImageLoader } from "../core/resolve.js";
31+
import { IgnorableError } from "../utils/error.js";
3132
import { debug, error } from "./logger.js";
3233
import { optimizeImage } from "./plugins/image-optimization/image-optimization.js";
3334
import { setNodeEnv } from "./util.js";
@@ -114,44 +115,54 @@ export async function defaultHandler(
114115
);
115116
return buildSuccessResponse(result, options?.streamCreator, etag);
116117
} catch (e: any) {
117-
error("Failed to optimize image", e);
118-
119-
// Determine appropriate status code based on error
118+
// Determine if this is a client error (4xx) or server error (5xx)
120119
let statusCode = 500; // Default to 500 for unknown errors
121-
let errorMessage = "Internal server error";
120+
const isClientError =
121+
e &&
122+
typeof e === "object" &&
123+
(("statusCode" in e &&
124+
typeof e.statusCode === "number" &&
125+
e.statusCode >= 400 &&
126+
e.statusCode < 500) ||
127+
e.code === "ENOTFOUND" ||
128+
e.code === "ECONNREFUSED" ||
129+
(e.message &&
130+
(e.message.includes("403") ||
131+
e.message.includes("404") ||
132+
e.message.includes("Access Denied") ||
133+
e.message.includes("Not Found"))));
134+
135+
// Only log actual server errors as errors, log client errors as debug
136+
if (isClientError) {
137+
debug("Client error in image optimization", e);
138+
} else {
139+
error("Failed to optimize image", e);
140+
}
122141

123-
// Check if error has HTTP status information
142+
// Determine appropriate status code based on error type
124143
if (e && typeof e === "object") {
125144
if ("statusCode" in e && typeof e.statusCode === "number") {
126145
statusCode = e.statusCode;
127-
errorMessage = `HTTP Error: ${statusCode}`;
128146
} else if ("code" in e) {
129147
const code = e.code as string;
130148
if (code === "ENOTFOUND" || code === "ECONNREFUSED") {
131149
statusCode = 404;
132-
errorMessage = `Image not found: ${e.message}`;
133150
}
134-
}
135-
136-
if (e.message && typeof e.message === "string") {
137-
// Try to extract status codes from error messages
151+
} else if (e.message) {
138152
if (e.message.includes("403") || e.message.includes("Access Denied")) {
139153
statusCode = 403;
140-
errorMessage = `Access denied: ${e.message}`;
141154
} else if (
142155
e.message.includes("404") ||
143156
e.message.includes("Not Found")
144157
) {
145158
statusCode = 404;
146-
errorMessage = `Image not found: ${e.message}`;
147-
} else {
148-
errorMessage = e.message;
149159
}
150160
}
151161
}
152162

163+
// Pass through the original error message from Next.js
153164
return buildFailureResponse(
154-
errorMessage,
165+
e.message || "Internal server error",
155166
options?.streamCreator,
156167
statusCode,
157168
);
@@ -318,18 +329,25 @@ async function downloadHandler(
318329
});
319330
});
320331

321-
request.on("error", (err: NodeJS.ErrnoException) => {
322-
error("Failed to get image", err);
323-
// Handle common network errors
324-
if (err && typeof err === "object" && "code" in err) {
325-
if (err.code === "ENOTFOUND" || err.code === "ECONNREFUSED") {
326-
res.statusCode = 404;
327-
} else {
328-
res.statusCode = 400;
329-
}
332+
request.on("error", (err: Error & { code?: string }) => {
333+
// For network errors, these are typically client errors (bad URL, etc.)
334+
// so log as debug instead of error to avoid false alarms
335+
const isClientError =
336+
err.code === "ENOTFOUND" || err.code === "ECONNREFUSED";
337+
338+
if (isClientError) {
339+
// Log the full error for debugging but don't expose it to the client
340+
debug("Client error fetching image", {
341+
code: err.code,
342+
message: err.message,
343+
});
344+
res.statusCode = 404; // Not Found for DNS or connection errors
330345
} else {
331-
res.statusCode = 400;
346+
error("Failed to get image", err);
347+
res.statusCode = 400; // Bad Request for other errors
332348
}
349+
350+
// Don't send the error message back to the client
333351
res.end();
334352
});
335353
}
@@ -357,6 +375,48 @@ async function downloadHandler(
357375
}
358376
}
359377
} catch (e: any) {
378+
// Check if this is a client error (like 404, 403, etc.)
379+
const isClientError =
380+
e &&
381+
typeof e === "object" &&
382+
(("statusCode" in e &&
383+
typeof e.statusCode === "number" &&
384+
e.statusCode >= 400 &&
385+
e.statusCode < 500) ||
386+
e.code === "ENOTFOUND" ||
387+
e.code === "ECONNREFUSED" ||
388+
(e.message &&
389+
(e.message.includes("403") ||
390+
e.message.includes("404") ||
391+
e.message.includes("Access Denied") ||
392+
e.message.includes("Not Found"))));
393+
394+
if (isClientError) {
395+
debug("Client error downloading image", e);
396+
// Just pass through the original error to preserve Next.js's error handling
397+
// but wrap it in IgnorableError to prevent it from being logged as an error
398+
const clientError = new IgnorableError(
399+
e.message || "Client error downloading image",
400+
);
401+
402+
// Preserve the original status code or set an appropriate one
403+
if (e && typeof e === "object") {
404+
if ("statusCode" in e && typeof e.statusCode === "number") {
405+
(clientError as any).statusCode = e.statusCode;
406+
} else if (e.code === "ENOTFOUND" || e.code === "ECONNREFUSED") {
407+
(clientError as any).statusCode = 404;
408+
} else if (e.message?.includes("403")) {
409+
(clientError as any).statusCode = 403;
410+
} else if (e.message?.includes("404")) {
411+
(clientError as any).statusCode = 404;
412+
} else {
413+
(clientError as any).statusCode = 400;
414+
}
415+
}
416+
417+
throw clientError;
418+
}
419+
360420
error("Failed to download image", e);
361421
throw e;
362422
}

0 commit comments

Comments
 (0)