vercel / vercel/next.js

`opengraph-image` route cannot use Node.js APIs in the `nodejs` runtime if its corresponding Page is in the `edge` runtime

Open
#77,796 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Metadata Module Resolution Runtime
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
  1. Create app/page.tsx, use edge runtime
  2. Add app/opengraph-image.tsx, load custom font, following the docs (requires nodejs runtime)
  3. Run next dev and open / page
  4. Get a Native module not found: node:fs/promises error
  5. Open /opengraph-image route manually
  6. 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-image manually, the OG image renders successfully, but a similar error is logged in the next dev stdout
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.