Skip to article body
A knife sharpener on a Cairo street working a grinding wheel on his bicycle, sparks streaking with the slow shutter
A knife sharpener on a Cairo street working a grinding wheel on his bicycle, sparks streaking with the slow shutter

Web Development · Field Notes

Could Not Load the sharp Module

Our share images worked in development and failed in production, because of which section of package.json listed sharp.

By Shehab Waleed, Founder, Cairo Studio 2 min read

Our site renders a share image for each page: the page title set in our typeface on a 1200 by 630 card. satori turns the layout into an SVG and sharp turns that into a JPEG. It worked in development, and the first request in production returned a 500 with one line in the log.

Error: Could not load the "sharp" module

How adapter-node builds your server

SvelteKit’s Node adapter does not ship node_modules as they are. It bundles the server code with Rollup so that a deployment only needs its production dependencies. The adapter’s source says so in a comment.

// we bundle the Vite output so that deployments only need
// their production dependencies. Anything in devDependencies
// will get included in the bundled code

The packages listed under dependencies are marked external and loaded from node_modules at runtime. Everything else is copied into the bundle. sharp had ended up under devDependencies.

Why sharp cannot be bundled

sharp is a wrapper around libvips, a native image library. At runtime it loads a compiled binary for the current operating system and processor, from a separate package that npm picks during install. Rollup can copy JavaScript into a bundle. It cannot copy that binary, and the bundled sharp had no way to find one.

The fix

"dependencies": {
"sharp": "^0.33.5"
}

With sharp in dependencies, the adapter leaves it external. Our deploy runs npm install on the server, which installs the binary for Linux on that machine, and the route has served images since.

The rule we use now

With adapter-node, the split between dependencies and devDependencies is not bookkeeping. It decides what gets bundled. Anything the server imports at runtime goes in dependencies when it has native parts, or when it reads files from its own package folder. Pure JavaScript usually survives bundling either way, but we no longer rely on that.

We have not tested every native package, so treat the list below as our experience rather than a full survey. Deploys taught us another lesson in the same month, when a deploy put noindex on eight pages.

Share
X LinkedIn Facebook WhatsApp Email
A hand holding a blade to a spinning grinding stone, sparks fanning out

It needs the machine, not just the blade.

In devDependencies
Bundled, usually works
In dependencies
Loaded from node_modules
In devDependencies
Bundled without its binary, fails
In dependencies
Loaded from node_modules, works
In devDependencies
The right place
In dependencies
Works, but adds weight to production installs

Based on how adapter-node marks dependencies as external in its build step.

Move sharp from devDependencies to dependencies, reinstall, and rebuild. adapter-node then leaves it external, and installing on the server fetches the right binary.

No. It bundles your server code and leaves packages listed in dependencies external, so the server needs npm install with production dependencies.

Each adapter bundles differently. Serverless adapters trace files instead of bundling them. Check your adapter’s documentation for how it treats native modules.

We build and run modern framework sites end to end, from the CMS model to the server they deploy on.

See our framework work

More from the Journal

view all →