`opengraph-image` route cannot use Node.js APIs in the `nodejs` runtime if its corresponding Page is in the `edge` runtime
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://codesandbox.io/p/devbox/zen-panini-rxx6zk
To Reproduce
- Create
app/page.tsx, useedgeruntime - Add
app/opengraph-image.tsx, load custom font, following the docs (requiresnodejsruntime) - Run
next devand open/page - Get a
Native module not found: node:fs/promiseserror - Open
/opengraph-imageroute manually - Get a rendered image (200 OK) with an error (warning) logged to the dev server console
Current vs. Expected behavior
Current behavior
If a page.tsx uses the edge runtime, but its opengraph-image.tsx uses the nodejs runtime, it appears as though opengraph-image.tsx is being imported once inside NodeJS and a second time inside the Edge environment. Since the example imports NodeJS-specific modules and uses process.cwd() APIs, it fails to load in the Edge environment. It still manages to render the OG image successfully when visiting the route manually, but it fails to build and errors are logged in the next dev stdout.
If we use the edge runtime for both files, but keep the NodeJS-specific modules and APIs, image generation fails completely.
In dev server
- The
/page fails to load, because the Edge runtime tries to load NodeJS modules:
See error message
○ Compiling / ...
✓ Compiled / in 7.5s
⨯ Error: Failed to load external module node:fs/promises: TypeError: Native module not found: node:fs/promises
at [project]/app/opengraph-image.tsx [app-edge-rsc] (ecmascript) (app/opengraph-image.tsx:2:0)
at [project]/app/opengraph-image--metadata.js [app-edge-rsc] (ecmascript) (app/opengraph-image--metadata.js:1:0)
1 | import { ImageResponse } from "next/og";
> 2 | import { readFile } from "node:fs/promises";
3 | import { join } from "node:path";
4 |
5 | export const runtime = "nodejs";
⨯ Error: Failed to load external module node:fs/promises: TypeError: Native module not found: node:fs/promises
at [project]/app/opengraph-image.tsx [app-edge-rsc] (ecmascript) (app/opengraph-image.tsx:2:0)
at [project]/app/opengraph-image--metadata.js [app-edge-rsc] (ecmascript) (app/opengraph-image--metadata.js:1:0)
1 | import { ImageResponse } from "next/og";
> 2 | import { readFile } from "node:fs/promises";
3 | import { join } from "node:path";
4 |
5 | export const runtime = "nodejs";
⚠ ./app/opengraph-image.tsx:8:8
Ecmascript file had an error
6 |
7 | const geistSemiBold = readFile(
> 8 | join(process.cwd(), "assets/Geist-SemiBold.ttf")
| ^^^^^^^^^^^
9 | );
10 |
11 | export default async function Image() {
A Node.js API is used (process.cwd at line: 8) which is not supported in the Edge Runtime.
Learn more: https://nextjs.org/docs/api-reference/edge-runtime
⚠ ./app/opengraph-image.tsx:2:1
Ecmascript file had an error
1 | import { ImageResponse } from "next/og";
> 2 | import { readFile } from "node:fs/promises";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | import { join } from "node:path";
4 |
5 | export const runtime = "nodejs";
A Node.js module is loaded ('node:fs/promises' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
⚠ ./app/opengraph-image.tsx:3:1
Ecmascript file had an error
1 | import { ImageResponse } from "next/og";
2 | import { readFile } from "node:fs/promises";
> 3 | import { join } from "node:path";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 |
5 | export const runtime = "nodejs";
6 |
A Node.js module is loaded ('node:path' at line 3) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
○ Compiling /_error ...
✓ Compiled /_error in 2.1s
GET / 500 in 10284ms
⚠ Cross origin request detected from rxx6zk-3000.csb.app to /_next/* resource. In a future major version of Next.js, you will need to explicitly configure "allowedDevOrigins" in next.config to allow this.
Read more: https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins
⚠ ./app/opengraph-image.tsx:8:8
Ecmascript file had an error
6 |
7 | const geistSemiBold = readFile(
> 8 | join(process.cwd(), "assets/Geist-SemiBold.ttf")
| ^^^^^^^^^^^
9 | );
10 |
11 | export default async function Image() {
A Node.js API is used (process.cwd at line: 8) which is not supported in the Edge Runtime.
Learn more: https://nextjs.org/docs/api-reference/edge-runtime
⚠ ./app/opengraph-image.tsx:2:1
Ecmascript file had an error
1 | import { ImageResponse } from "next/og";
> 2 | import { readFile } from "node:fs/promises";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | import { join } from "node:path";
4 |
5 | export const runtime = "nodejs";
A Node.js module is loaded ('node:fs/promises' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
⚠ ./app/opengraph-image.tsx:3:1
Ecmascript file had an error
1 | import { ImageResponse } from "next/og";
2 | import { readFile } from "node:fs/promises";
> 3 | import { join } from "node:path";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 |
5 | export const runtime = "nodejs";
6 |
A Node.js module is loaded ('node:path' at line 3) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- If we visit
/opengraph-imagemanually, the OG image renders successfully, but a similar error is logged in thenext devstdout
See error message
○ Compiling /opengraph-image ...
✓ Compiled /opengraph-image in 1084ms
⚠ ./app/opengraph-image.tsx:8:8
Ecmascript file had an error
6 |
7 | const geistSemiBold = readFile(
> 8 | join(process.cwd(), "assets/Geist-SemiBold.ttf")
| ^^^^^^^^^^^
9 | );
10 |
11 | export default async function Image() {
A Node.js API is used (process.cwd at line: 8) which is not supported in the Edge Runtime.
Learn more: https://nextjs.org/docs/api-reference/edge-runtime
⚠ ./app/opengraph-image.tsx:2:1
Ecmascript file had an error
1 | import { ImageResponse } from "next/og";
> 2 | import { readFile } from "node:fs/promises";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | import { join } from "node:path";
4 |
5 | export const runtime = "nodejs";
A Node.js module is loaded ('node:fs/promises' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
⚠ ./app/opengraph-image.tsx:3:1
Ecmascript file had an error
1 | import { ImageResponse } from "next/og";
2 | import { readFile } from "node:fs/promises";
> 3 | import { join } from "node:path";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 |
5 | export const runtime = "nodejs";
6 |
A Node.js module is loaded ('node:path' at line 3) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
GET /opengraph-image 200 in 1625ms
On build
- If we run
next build, webpack fails
See build logs
@ build /project/workspace
> next build
▲ Next.js 15.3.0-canary.32
Creating an optimized production build ...
Failed to compile.
node:fs/promises
Module build failed: UnhandledSchemeError: Reading from "node:fs/promises" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
at /project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:408376
at Hook.eval [as callAsync] (eval at create (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:6:1)
at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:6378)
at Object.processResource (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:408301)
at processResource (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:5308)
at iteratePitchingLoaders (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:4667)
at runLoaders (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:8590)
at NormalModule._doBuild (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:408163)
at NormalModule.build (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:410176)
at /project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:82494
Import trace for requested module:
node:fs/promises
./app/opengraph-image.tsx?__next_metadata_image_meta__
./node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/build/webpack/loaders/next-metadata-image-loader.js?type=openGraph&segment=&basePath=&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js!./app/opengraph-image.tsx?__next_metadata__
./node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.js?name=app%2Fpage&page=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2Fproject%2Fworkspace%2Fapp&appPaths=%2Fpage&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&preferredRegion=&middlewareConfig=e30%3D!./app/page.tsx?__next_edge_ssr_entry__
node:path
Module build failed: UnhandledSchemeError: Reading from "node:path" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
at /project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:408376
at Hook.eval [as callAsync] (eval at create (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:6:1)
at Object.processResource (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:408301)
at processResource (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:5308)
at iteratePitchingLoaders (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:4667)
at runLoaders (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:8590)
at NormalModule._doBuild (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:408163)
at NormalModule.build (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:410176)
at /project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:82494
at NormalModule.needBuild (/project/workspace/node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:414126)
Import trace for requested module:
node:path
./app/opengraph-image.tsx?__next_metadata_image_meta__
./node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/build/webpack/loaders/next-metadata-image-loader.js?type=openGraph&segment=&basePath=&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js!./app/opengraph-image.tsx?__next_metadata__
./node_modules/.pnpm/next@15.3.0-canary.32_react-dom@19.0.0_react@19.0.0/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.js?name=app%2Fpage&page=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2Fproject%2Fworkspace%2Fapp&appPaths=%2Fpage&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&preferredRegion=&middlewareConfig=e30%3D!./app/page.tsx?__next_edge_ssr_entry__
> Build failed because of webpack errors
ELIFECYCLE Command failed with exit code 1.
runtime export combinations
I tried all the combinations of runtime config exports for page.tsx and opengraph-image.tsx to investigate the behavior.
See behavior table
page.tsx runtime export |
opengraph-image.tsx runtime export |
/ page dev behavior |
/opengraph-image route dev behavior |
build behavior |
|---|---|---|---|---|
| No export | No export | 200, no errors | 200, no errors | completes |
| No export | nodejs |
200, no errors | 200, no errors | completes |
| No export | edge |
200, no errors | 500, node:fs/promises Native module not found error |
fails |
nodejs |
No export | 200, no errors | 200, no errors | completes |
nodejs |
nodejs |
200, no errors | 200, no errors | completes |
nodejs |
edge |
200, no errors | 500, node:fs/promises Native module not found error |
fails |
edge |
No export | 200, "not supported in edge runtime" logged in server | 500, node:fs/promises Native module not found error |
fails |
edge |
nodejs |
500, node:fs/promises Native module not found error |
200, "not supported in edge runtime" logged in server | fails |
edge |
edge |
500, node:fs/promises Native module not found error |
500, node:fs/promises Native module not found error |
fails |
Expected behavior
If it is possible, I would expect that the OG image route and the page itself to work in 2 different runtimes.
If it's not possible or this behavior is intended, I think the runtime configuration should be shared for the entire route segment (or at least the page and OG route). The docs should also mention this fact.
Provide environment information
Operating System:
Platform: linux
Arch: x64
Version: #1 SMP PREEMPT_DYNAMIC Sun Aug 6 20:05:33 UTC 2023
Available memory (MB): 4242
Available CPU cores: 2
Binaries:
Node: 20.9.0
npm: 9.8.1
Yarn: 1.22.19
pnpm: 8.10.2
Relevant Packages:
next: 15.3.0-canary.33 // Latest available version is detected (15.3.0-canary.33).
eslint-config-next: N/A
react: 19.0.0
react-dom: 19.0.0
typescript: 5.3.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Metadata, Module Resolution, Runtime
Which stage(s) are affected? (Select all that apply)
next build (local), next dev (local)
Additional context
The solution in this case would be to configure the same runtime for both. In my case, I fixed it by making the opengraph-image.tsx use edge and fetch()-ing the fonts: https://github.com/bvpav/nextjs-hurdles/commit/ef73fefad8f21e96015b5d83c651b71232e325de
However the fact that both files need to be in the same runtime was not obvious to me and feels like a bug, hence why I am not reporting this as a Docs issue.
Contributor 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.
Research direction
Reproduce the issue with app/page.tsx using the edge runtime and app/opengraph-image.tsx using nodejs, then run next dev and next build. Trace the metadata image import shown through next-metadata-image-loader and compare the page and image runtime handling. Done means the page loads, the image renders, and development and production builds no longer report Node.js APIs in the Edge runtime.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nextjs, node.js
- Domain
- build-system, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100