Working with Next.js can be smooth until a cryptic error stops your build. Recently, while building a Next.js + TypeScript project, I ran into an error related to the Sharp image-processing library. I’ll share the exact problem, how I figured it out, and the steps I took to fix and trace errors in a clean way.
The Error I Encountered
When I ran the build, I saw this dreaded message:
./node_modules/.pnpm/sharp@0.32.4/node_modules/sharp/build/Release/sharp-win32-x64.node
Module parse failed: Unexpected character '�' (1:2)
You may need an appropriate loader to handle this file type...
At first glance, this was confusing. But after digging deeper, I realized this was a Webpack error. Webpack was trying to parse .node
files (native binaries) as if they were JavaScript. Since .node
files contain compiled binary code, Webpack didn’t know what to do with them, soit threw an “Unexpected character” error.
Why This Happen
Here’s what I found out:
- Sharp is a native module
Thesharp-win32-x64.node
file is compiled code for Windows. Webpack cannot interpret it. - Next.js build process
In a pure Node.js environment,.node
files are fine. But when Next.js tries to bundle code for the client, Webpack accidentally grabs these files and breaks. - Key distinction
Sharp should only ever run server-side. If you import it inside a React component, you’ll hit this exact problem.
My First Fix Attempt
I tried a quick fix by modifying my next.config.js
file:
// next.config.js
webpack: (config, { isServer }) => {
if (!isServer) {
config.module.rules.push({
test: /\/sharp\//,
use: "ignore-loader",
});
}
return config;
}
This tells Webpack to ignore Sharp when bundling client-side code. It worked for a moment, but I quickly realized this was more of a band-aid solution.
A Safer Fix
The real fix was making sure Sharp is only imported and executed on the server.
Check your imports
At first, I made the mistake of importing Sharp in a React component:
// Don’t do this in client code
import sharp from "sharp";
function ImageConverter() {
sharp("input.png").toFile("output.webp");
return <p>Done!</p>;
}
This doesn’t work because the component runs in the browser.
Instead, I moved Sharp into a server utility function:
// src/utils/imageUtils.ts
import sharp from "sharp";
export async function convertToWebp(fileBuffer: Buffer) {
try {
return await sharp(fileBuffer).toFormat("webp").toBuffer();
} catch (err) {
console.error("Error converting to WebP:", err);
throw err;
}
}
Now, I can call convertToWebp
from an API route or inside getServerSideProps
.
Add error logging for tracing
When debugging, I added more detailed logs so I could see the full error trace:
import sharp from "sharp";
export async function convertToWebp(fileBuffer: Buffer) {
try {
return await sharp(fileBuffer).toFormat("webp").toBuffer();
} catch (err: any) {
console.error("[Sharp Error Trace]");
console.error("Message:", err.message);
console.error("Stack:", err.stack);
throw err;
}
}
This way, I could tell if the error was caused by:
- a binary mismatch,
- a missing dependency, or
- accidentally running Sharp in client code.
More Practice Functionality
To sharpen my debugging skills (pun intended), I built some extra utilities:
Fallback converter
If WebP conversion fails, fall back to PNG:
export async function safeConvert(fileBuffer: Buffer) {
try {
return await sharp(fileBuffer).toFormat("webp").toBuffer();
} catch {
console.warn("WebP failed, falling back to PNG");
return await sharp(fileBuffer).png().toBuffer();
}
}
Reusable Error Logger
I extracted a small traceError
utility so I didn’t repeat error logs everywhere:
export function traceError(error: any, context: string) {
console.error(`[${context}]`, error.message);
console.error(error.stack);
}
And then used it like this:
try {
const result = await convertToWebp(buffer);
} catch (err) {
traceError(err, "convertToWebp");
}
Testing Server Only Imports
I also added a test to confirm Sharp wasn’t sneaking into client builds. This ensured I only ever used it where it belongs—the server.
Final Thought
What I learned is that error tracing in Next.js starts with knowing where your code runs. The client and server environments are very different, and tools like Sharp are strictly server-side. When you see strange build errors like “Unexpected character ‘�’”, it usually means Webpack is trying to parse something it shouldn’t often a binary file. By restructuring imports, adding clear error logs, and practicing with fallbacks and utilities, I now feel much more confident debugging Next.js projects.