Next.js

Fix UnhandledSchemeError: Reading from "node:buffer"

Reproduced on Next.js 16.4.0 with webpack: which import triggers the error, why bare "buffer" builds but "node:buffer" does not, and the three fixes that compile.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

A module that ends up in the browser bundle imports a Node.js core module with the node: prefix, either directly or through a dependency such as node-fetch 3. Webpack reads only data: and file: URIs out of the box, so it stops on node:buffer. Keep that import in server code (a Server Component, a Route Handler, getServerSideProps), replace the Node API with a browser one, or rewrite the request in the webpack function of next.config.js.

The full message, as webpack prints it:

text
node:buffer
Module build failed: UnhandledSchemeError: Reading from "node:buffer" 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.

This is the error asked about in the Stack Overflow question UnhandledSchemeError: What is this node:buffer error and how do I solve it?, which also points to the Next.js discussion node:buffer, where most commenters name node-fetch as the package that pulled the import in.

What webpack is telling you#

The text is built by a single class in webpack, lib/errors/UnhandledSchemeError.js. Its constructor receives a scheme and a resource and produces exactly the three lines above: the resource could not be read, webpack supports data: and file: URIs by default, and you may need a plugin for this scheme. The class is still in webpack's source today, and reports on the Next.js tracker since 2021 show the same message.

So webpack is not saying that buffer is missing. It is saying that it parsed node:buffer as a URI, saw the scheme node, and found no plugin that knows how to read a resource with that scheme in this compilation.

Next.js runs webpack several times per build. The Custom Webpack Config page documents that the webpack function runs twice for the server (Node.js and Edge runtimes) and once for the client, and that isServer tells them apart. For the Node.js server compilation, Next.js turns on webpack's Node externals preset, so Node built-ins are left to the runtime, which is why the same import is harmless in server code. The client compilation has nothing to hand node: requests to.

Why bare "buffer" builds and "node:buffer" does not#

The client configuration in Next.js's webpack-config.ts sets a resolve.fallback table for the client compiler: buffer resolves to a bundled browser copy of the buffer package, and the same goes for path, stream, crypto, util and other core module names. That table is keyed by bare names. A request for node:buffer never matches it.

The reproduction below shows the difference with one change: replacing 'node:buffer' with 'buffer' in the same client component turns a failed build into a successful one.

This is deliberate. On the Next.js issue #28774, Tim Neutkens wrote that node: is not supported in client-side code because it is Node.js-only, and that Server Components or getServerSideProps/getStaticProps are fine while other places will still error. In a follow-up comment he added that "we eventually want to remove these default shims" because of their bundle size, and that node: is meant to signal "In the Node.js environment", hence no shims for it.

The Stack Overflow asker saw the error when adding a link to a local route. The link is rarely the cause: what matters is which module the client bundle reaches. The import trace under the error lists the chain from the dependency back to your file, and that chain is the one to read.

A minimal client component that fails#

The reproduction ran on 7 October 2026 in the official node:22-bookworm Docker image, with Node.js 22.23.3 and Next.js 16.4.0, App Router, built with next build --webpack. A Server Component page renders this client component:

jsx
// app/encoder.js
'use client'
import { Buffer } from 'node:buffer'
 
export default function Encoder() {
  return <p>{Buffer.from('hello').toString('base64')}</p>
}

The build stops, and the import trace points at the file:

text
Failed to compile.
 
node:buffer
Module build failed: UnhandledSchemeError: Reading from "node:buffer" 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.
...
 
Import trace for requested module:
node:buffer
./app/encoder.js

The dependency case, the one most people in the GitHub discussion hit, behaves the same way. With node-fetch 3.3.2 imported in the same client component, webpack reports an UnhandledSchemeError for each node: module it reaches inside the package; the last one printed in the reproduction was node:net, and the trace shows the path through node-fetch:

text
node:net
Module build failed: UnhandledSchemeError: Reading from "node:net" 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.
...
 
Import trace for requested module:
node:net
./node_modules/node-fetch/src/utils/referrer.js
./node_modules/node-fetch/src/index.js
./app/encoder.js

The package's entry file, src/index.js in node-fetch 3.3.2, imports node:http, node:https, node:zlib, node:stream and node:buffer at the top. So the module named in the error is not always node:buffer: a package such as node-fetch 3 produces one error per node: import. The fix is the same.

Fix: move the Node API out of the client bundle#

Pick the option that matches where the code really needs to run.

1. Run the code on the server#

If the component only needs the result, remove 'use client' and let it be a Server Component, or compute the value in a Server Component and pass it down as a prop. In the reproduction, the same node:buffer import without 'use client' builds cleanly:

jsx
// app/encoder.js — Server Component, no directive
import { Buffer } from 'node:buffer'
 
export default function Encoder() {
  return <p>{Buffer.from('hello').toString('base64')}</p>
}

The rule to keep in mind is that 'use client' marks a boundary in the import graph: every module the file imports is bundled for the browser. The page on use client vs use server explains where that boundary falls, and why 'use client' belongs on the leaf interactive component rather than on a layout or page that drags the whole subtree into the browser bundle. In the Pages Router, the equivalent is to call the Node API inside getServerSideProps or getStaticProps only, as the comment on issue #28774 above describes.

2. Replace node-fetch with the global fetch#

Most reports in the GitHub discussion come from node-fetch 3. A Next.js app does not need it: fetch is a browser global, and on issue #32935, a Next.js 12 report of the same error for node:fs with node-fetch, Tim Neutkens answered that fetch is supported by default and node-fetch does not need to be installed. Remove the import and keep the call:

jsx
'use client'
 
export default function Encoder() {
  return <button onClick={() => fetch('https://example.com')}>Load</button>
}

That version builds with webpack in the reproduction. Then remove the package with npm uninstall node-fetch if nothing else imports it.

The second answer on Stack Overflow suggests downgrading node-fetch to a 2.x release. In the reproduction, node-fetch 2.7.0 in the same client component also compiled. The reason is in its package.json: version 2 declares a browser entry, browser.js, and that file exports the global object's fetch. In the browser bundle the downgrade therefore gives you the global fetch under another name, which is one more reason to call it directly.

3. Strip the prefix in the client compilation (last resort)#

When the node: import sits in a dependency you cannot change, and the browser polyfill for the bare module is acceptable, you can rewrite node:x to x so the fallback table above picks it up. This is the workaround posted on issue #28774, written here with the bundler instance Next.js passes to the webpack function and limited to the client compiler:

js
// next.config.js
module.exports = {
  webpack: (config, { webpack, isServer }) => {
    if (!isServer) {
      config.plugins.push(
        new webpack.NormalModuleReplacementPlugin(/^node:/, (resource) => {
          resource.request = resource.request.replace(/^node:/, '')
        })
      )
    }
    return config
  },
}

With this config, the failing node:buffer component builds with webpack in the reproduction. Two caveats. Only modules that have an entry in that table get a polyfill; fs and net have none, so a package that really opens sockets or reads files cannot be rescued this way. And the Next.js docs warn that changes to the webpack config are not covered by semver, while the comment quoted above says the default shims may eventually go.

The first Stack Overflow answer, with react-app-rewired and config-overrides.js, is for Create React App: its scripts block runs react-app-rewired start. It does not apply to Next.js, where the webpack key of next.config.js is the documented place for this kind of change.

Confirm the build is clean#

Run the same build that failed, with webpack:

bash
npx next build --webpack

A fixed build compiles and prints the route table instead of Failed to compile.. In the reproduction, each of the three fixes above produced:

text
Route (app)
┌ ○ /
└ ○ /_not-found

If the error now names a different node: module, read its import trace again: a second dependency may be pulling in its own Node-only code.

Webpack versus Turbopack#

Next.js 16 builds with Turbopack by default; the reproduction selected webpack with the webpack flag of next build. In the reproduction, the exact client component that fails under webpack compiled with the default Turbopack build:

text
▲ Next.js 16.4.0 (Turbopack)
...
✓ Compiled successfully in 7.0s

That only shows the build succeeds. It does not prove that the Node API behaves the same in a browser, so switching bundlers is not a fix for code that really needs Node. If your project builds with webpack, the error applies to you whatever the Next.js version.

The same class of mistake has other symptoms in Next.js. A Node-only import in code bundled for the Edge runtime is one of the causes listed in Next.js middleware not running on Vercel, and importing react-dom/server into the wrong layer gives the compile error explained in the react-dom/server import error in the App Router. If, after moving a fetch dependency around, the build instead reports Module not found: Can't resolve 'encoding' from a node-fetch 2 chain such as cross-fetch, that is a different resolution failure, covered in Fix Module not found: Can't resolve 'encoding'.

The thread that runs through all of these is the bundle a module lands in. UnhandledSchemeError is webpack's way of saying that a node: import reached the browser bundle; once the import stays on the server, or the browser gets an API it actually has, the scheme never reaches webpack and the message goes away.

Further reading: Fix Next.js: Unable to Acquire the Dev Server Lock.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.