[@nuxt/image 2.2.0] `/_ipx` route returns 500 on Vercel: ERR_REQUIRE_ESM when ipx 4 requires `srvx/node` (Nitro v2 legacy route)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 50/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- node.js, nuxt, typescript
Research direction
Start by reading dist/runtime/server/routes/_ipx.legacy.js and tracing its createIPXNodeHandler() call into ipx 4's createRequire("srvx/node") path. Reproduce the ERR_REQUIRE_ESM on a Vercel deployment using the listed Nuxt 4 and Nitro v2 versions, then compare a fix with the suggested dynamic import and h3 adapter options. Done means on-demand /_ipx requests return successfully on Vercel; the issue names no test file.
Written by the indexing model from the issue text.
Description
Environment
- Runtime: Vercel Serverless Functions, Node.js 24.x
- Build: Vercel (preset
vercel, 2 vCPU / 8 GB build machine); local dev machine: Windows 11, Node 24.21.0 - Package manager: npm
- Framework: [email protected] ([email protected], Nitro v2)
- Affected package: @nuxt/[email protected]
- Resolved: [email protected], [email protected], [email protected]
- Last working combination (no ESM error): @nuxt/[email protected] with [email protected]
Steps to reproduce
- Use a Nuxt 4.6 app (Nitro v2) with
@nuxt/imageand theipxprovider, deployed to Vercel. - Upgrade
@nuxt/imageto 2.2.0:
npm install @nuxt/[email protected] --save-exact
npm ls ipx srvx sharp
- Deploy to Vercel (the build succeeds).
- Request an image size that was not prerendered, so the
/_ipxserver route runs inside the function:
curl -i "https://my-app.vercel.app/_ipx/q_70&s_123x38/img/example.png"
Describe the bug
Since 2.2.0 (upgrade to ipx v4, #2352), every runtime request to the /_ipx route on Vercel fails with a 500 ERR_REQUIRE_ESM. Prerendered /_ipx URLs are served as static files and still work.
With Nitro v2, @nuxt/image uses the legacy route dist/runtime/server/routes/_ipx.legacy.js, which calls createIPXNodeHandler() from ipx. In ipx 4.0.0, createIPXNodeHandler() loads the ESM-only srvx package synchronously through createRequire:
// node_modules/ipx/dist/_chunks/node-fs.mjs ([email protected])
function requireModule(id) {
const { createRequire } = globalThis.process.getBuiltinModule("node:module");
return createRequire(import.meta.url)(id);
}
function createIPXNodeHandler(ipx, opts) {
const { toNodeHandler } = requireModule("srvx/node");
return toNodeHandler(createIPXFetchHandler(ipx, opts));
}
[email protected] is "type": "module" and ./node maps to ./dist/adapters/node.mjs. On plain Node 24 this works, because require(esm) is supported: locally the same createRequire(...)("srvx/node") call returns toNodeHandler without errors. On Vercel the module load goes through the platform loader (/opt/rust/nodejs.js), which throws ERR_REQUIRE_ESM instead. The traced file is present in the function bundle (/var/task/node_modules/srvx/dist/adapters/node.mjs), so this is not a tracing or missing-file problem.
The non-legacy route (_ipx.js) uses createIPXFetchHandler(), which does not touch srvx, so Nitro v3 setups are probably not affected. I have not verified this.
Possible fixes (suggestions only):
// ipx: load srvx with a dynamic import instead of createRequire
async function createIPXNodeHandler(ipx, opts) {
const { toNodeHandler } = await import("srvx/node");
return toNodeHandler(createIPXFetchHandler(ipx, opts));
}
// or @nuxt/image legacy route: use createIPXFetchHandler with h3 v1's fromWebHandler,
// avoiding the srvx Node adapter entirely
Logs
λ GET /_ipx/q_70&s_123x38/img/example.png
[request error] [unhandled] [GET] https://my-app.vercel.app/_ipx/q_70&s_123x38/img/example.png
Error [ERR_REQUIRE_ESM]: require() of ES Module /var/task/node_modules/srvx/dist/adapters/node.mjs from /var/task/node_modules/ipx/dist/_chunks/node-fs.mjs not supported.
Instead change the require of /var/task/node_modules/srvx/dist/adapters/node.mjs to a dynamic import() which is available in all CommonJS modules.
at /opt/rust/nodejs.js:2:14747
at Function._o (/opt/rust/nodejs.js:2:15125)
at e.<computed>.pt._load (/opt/rust/nodejs.js:2:14717) {
cause: Error [ERR_REQUIRE_ESM]: require() of ES Module /var/task/node_modules/srvx/dist/adapters/node.mjs from /var/task/node_modules/ipx/dist/_chunks/node-fs.mjs not supported.
...
code: 'ERR_REQUIRE_ESM'
},
statusCode: 500,
fatal: false,
unhandled: true
}
+-- @nuxt/[email protected]
| `-- [email protected]
| +-- [email protected] deduped
| `-- [email protected]
`-- [email protected]
+-- @nuxt/[email protected]
| +-- [email protected]
Additional context
- @nuxt/image v2.2.0 release notes ("upgrade to released ipx v4", #2352): https://github.com/nuxt/image/releases/tag/v2.2.0
- The affected code is in ipx (
createIPXNodeHandlerindist/_chunks/node-fs.mjs). It may need to be fixed there (https://github.com/h3js/ipx) as well as in the@nuxt/imagelegacy route. - The build and all prerendered images work fine: about 150 prerendered
/_ipxURLs on the deployed site return 200. Only image sizes generated on demand at runtime are affected. - With @nuxt/image 2.0.0 / ipx 3.1.1, the same runtime route loaded correctly. It returned 404
IPX_FILE_NOT_FOUNDbecausepublic/files are not available inside the Vercel function, but there was no ESM error.
- Dominant language
- TypeScript
- Stars
- 1.6k
- Forks
- 329
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 15
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from nuxt/image
-
Difficulty 3/5 1-2 days Newbie friendliness 52/100
Maintainers usually reply within 1 day
-
Bun.Image as an additional, self-hosted provider alongside ipxPossibly taken @nathanchase claimed this 48 days ago. Open
Difficulty 5/5 Over a week Newbie friendliness 48/100
Maintainers usually reply within 1 day
-
sizes: an unprefixed fluid value below 50vw emits an invalid 0w srcset descriptorMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 3/5 1-2 days Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
IPX provider results 500 error in the production using @nuxt/image ^2.1.0Possibly taken @chairulakmal claimed this 34 days ago. Open
Difficulty 3/5 1-2 days Newbie friendliness 55/100
nuxt/image#2307 · 5 comments · 9 reactions ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 55/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
paperclipai/paperclip#17151 ·
Maintainers usually reply within 1 day
-
needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
AgoraIO-Extensions/agent-infra#3159 ·
Maintainers usually reply within 1 day
-
blocklist addition
Difficulty 1/5 Under an hour Newbie friendliness 66/100
MetaMask/eth-phishing-detect#304596 ·
Maintainers usually reply within 1 day
-
friction
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
kentcdodds/kody#4142 ·
-
[Bug] Statistics | Task statistics are always empty on MikroORM with MySQL or SQLitePossibly taken @NdekoCode claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
ever-co/ever-gauzy#11214 ·
Maintainers usually reply within 1 day