Fix: Cannot set property Request of #<Object> getter
Reproduced line by line with Next.js's own polyfill file and abortcontroller-polyfill's published source across Node 16, 17 and 18, plus which of two independent fixes actually applies.
TL;DR#
Next.js's own Node.js-side fetch polyfill defines a lazy, getter-only Request on the global
object whenever global.fetch is missing — true for Node.js below 18. Any dependency that still
tries to patch the fetch globals itself, such as abortcontroller-polyfill, then does a plain
Request = … assignment against that getter-only property and throws, because an accessor with
no setter cannot be overwritten. The fix is to either stop that dependency from loading
abortcontroller-polyfill (for web3, upgrade to 1.8.2 or later, which guards the load) or
move the build to Node.js 18 or newer, where Next.js's own polyfill never installs in the first
place.
TypeError: Cannot set property Request of #<Object> which has only a getterThat is the exact message from a Stack Overflow question:
a TypeScript Next.js app that built and ran fine locally, then failed this way only when deployed
to Vercel. The trace underneath it runs straight through
node_modules/abortcontroller-polyfill/dist/polyfill-patch-fetch.js, which is the actual file
this clash lives in, regardless of which project dependency pulled it in.
Why it happens#
Next.js 13.0.x shipped its own fallback so that server-side code could call fetch() without the
caller worrying about which Node.js version would run it. That fallback is one real file,
node-polyfill-fetch.js. Here is the relevant part of the source for next@13.0.2
(the version named in the GitHub thread below). The logic is identical in the package this
article's own reproduction actually installed from npm: diffing this source against the real
node_modules/next/dist/server/node-polyfill-fetch.js it produced shows the same if (!global.fetch) guard and the same three getter-only properties, apart from formatting and an
appended source-map comment:
// excerpt — apart from formatting, identical in the published package (see text above)
if (!global.fetch) {
// … global.fetch is assigned a plain, reassignable function here …
Object.defineProperties(global, {
Headers: { get() { return getFetchImpl().Headers } },
Request: { get() { return getRequestImpl() } },
Response: { get() { return getFetchImpl().Response } },
})
}Two things matter in that snippet. First, the whole block is skipped when global.fetch already
exists. Second, Headers, Request and Response are installed with Object.defineProperties
and only a get() — no set, and configurable left at its default of false. That is the
textbook shape of a property JavaScript refuses to let you overwrite: an accessor with a getter
and no setter, non-configurable. Reassigning it from strict-mode code throws exactly
TypeError: Cannot set property X of #<Object> which has only a getter, with X being whatever
you tried to reassign — and the code that does the reassigning below opens with a 'use strict'
directive of its own. A related Node.js-18-only failure
on this site comes from the same transition, where the new native fetch() turned out to be
stricter, not more lenient, than whatever shipped before it.
abortcontroller-polyfill exists to add AbortController/AbortSignal support to whatever
fetch() it finds. The published 1.7.8 release
wraps the whole file in a UMD header, and the function that does the actual patching (lines
588-614 of that same file) opens with its own early exit:
// abortcontroller-polyfill@1.7.8, lines 588-614
(function (self) {
if (!polyfillNeeded(self)) {
return
}
// … the patch runs here …
})(typeof self !== 'undefined' ? self : global)Node.js has no browser self, so that IIFE always runs against global there. Inside that
function, after the early exit, it does this:
// abortcontroller-polyfill@1.7.8 — dist/polyfill-patch-fetch.js, lines 600-601
self.fetch = fetch
self.Request = Requestself.fetch = fetch is harmless — Next's global.fetch is a plain function, happy to be
replaced. self.Request = Request is the direct, unconditional assignment that hits Next's
getter-only accessor and throws. The library is clearly aware that getter-only properties exist
in this space — a comment earlier in the same file already warns about a near-identical
TypeError on a Request instance's own signal property — but that awareness never extended
to guarding this particular global assignment, at least not in this file.
A minimal reproduction, run on 25 October 2026 with a throwaway npm install of exactly these two
published packages (next@13.0.2 and abortcontroller-polyfill@1.7.8), nothing else — no Vercel,
no TypeScript, no web3 — confirms it: requiring next/dist/server/node-polyfill-fetch.js and
then abortcontroller-polyfill/dist/polyfill-patch-fetch.js throws the exact error above on
node:16-alpine (v16.20.2, the same major named in the GitHub issue below) and on
node:17.4.0-alpine. Object.getOwnPropertyDescriptor(global, 'Request') on those two images
reports {"hasGet":true,"hasSet":false,"configurable":false} right before the throw. On that same
Node.js 16.20.2, AbortController is already a native function, but
Request.prototype.hasOwnProperty('signal') is false — abortcontroller-polyfill's own
internal check looks at both, and only the second one still says a patch is needed.
The same two npm installs on node:18-alpine (v18.20.8) require cleanly, but not because
abortcontroller-polyfill successfully reassigns anything. There,
Request.prototype.hasOwnProperty('signal') is true — Node's own docs mark fetch,
Request, Response and Headers as on by default
from Node.js 18 — so abortcontroller-polyfill's own pre-check decides no patch is needed at all
and returns before touching Request, confirmed by comparing global.Request's own reference
before and after the require(), which comes back unchanged. Next's polyfill file never installs
its getter here either, for the separate reason already given: global.fetch already exists when
it runs, so its if (!global.fetch) guard skips the whole block.
The original GitHub issue, filed against web3.js
because that was the dependency the first reporters had in common, was narrowed by another
commenter to the same two variables: web3@1.8.0 plus Next.js 13.0.2 breaks on Node.js 16 and
works on Node.js 18; web3@1.7.4 plus the same Next.js works on either. Requiring web3 itself —
not just the two isolated files above — confirms the same split, and narrows the version further:
web3@1.8.0 and web3@1.8.1 both throw on Node.js 16; web3@1.8.2 and the current 1.10.4 (the
highest 1.x release, from February 2024) do not. Diffing web3-providers-http's own published
source between those two releases shows exactly what changed, in the one line that matters:
// web3-providers-http@1.8.1 — unconditional
require('abortcontroller-polyfill/dist/polyfill-patch-fetch')// web3-providers-http@1.8.2 — guarded
if (typeof global !== 'undefined' && !global.AbortController) {
require('abortcontroller-polyfill/dist/polyfill-patch-fetch')
}1.8.2 added its own guard in front of the require, checking only global.AbortController —
not the Request.prototype.signal check abortcontroller-polyfill makes internally.
AbortController is already native on Node.js 16.20.2, confirmed above, so this guard is
satisfied on the exact Node.js version these reports used, not only on Node.js 18: across
every Node.js version this reproduction tested (16.20.2 through 18.20.8), the fix does not
depend on upgrading Node.js at all. next@13.0.2's own floor goes lower still, to Node.js
>=14.6.0; this reproduction did not test that range, so whether the same guard is satisfied
there too stays open. One commenter in that thread said, on 14 November 2022,
that they had opened a pull request; replying to a different developer hitting the same error on
a newer next release six weeks later, on 27 December, that same commenter said the fix would be
"available in the next version update" rather than confirming it had already shipped.
web3.js's own changelog
records the result under 1.8.2 (released 30 January 2023, per the npm registry): "Fixed
Next.js builds failing on Node.js v16, Abortcontroller added if it doesn't exist globally
(#5601)" — naming this exact issue.
Fix#
1. Find what pulls in the old polyfill#
npm ls abortcontroller-polyfillA project on web3 1.8.0 to 1.8.2 (the releases this article checked) reports a chain like
this, whether or not that particular version actually triggers the crash —
abortcontroller-polyfill stays a dependency of web3-providers-http even after 1.8.2 guards
the require that loads it; 1.7.4 has no such dependency at all:
`-- web3@1.8.2
`-- web3-core@1.8.2
`-- web3-core-requestmanager@1.8.2
`-- web3-providers-http@1.8.2
`-- abortcontroller-polyfill@1.7.8So presence alone does not say whether the require actually runs. If npm ls abortcontroller-polyfill prints nothing at all, no package in your tree can trigger this
specific clash. If it does print, check the version of the package one level above it instead —
for web3, that is web3-providers-http, and 1.8.2 or later is the one that matters.
2. Change the dependency, if you can#
For web3 specifically, the simplest fix is an upgrade within the same 1.x major: web3@^1.8.2
resolves to the current 1.10.4 today, which this article's own reproduction loaded cleanly:
npm install web3@^1.8.2Pinning back to 1.7.4 — what the original reports actually did, before 1.8.2 existed — works
too, for the same reason: neither version loads abortcontroller-polyfill at all. The current
web3 major, 4.16.0, has an empty abortcontroller-polyfill tree as well, but 4.x is a new
major version, which may break 1.x code outright rather than merely fixing this one error;
treat it as a separate migration, not this fix.
3. Or change the Node.js version, independent of the dependency#
This is the fix that works regardless of which package patches fetch, because it removes Next's
getter-only Request instead of working around it. Make the Node.js version your editor, your
CI and your deployment target all agree on at least 18:
{
"engines": {
"node": ">=18.17.0"
}
}On Vercel, the same version can be set without touching package.json, from the project's
Settings → Build and Deployment → Node.js Version; an engines.node range in package.json
takes priority over that dashboard setting. Either way, confirm it by printing process.version in the
build itself rather than trusting what your editor's terminal reports — one developer in the
same GitHub thread spent ten hours on this before
realising their Netlify deploy needed its own Node.js version pin; adding a .node-version file
set to 19.1.0 in the project root fixed it for them.
Verify the fix#
Re-run the build (next build locally, or trigger a fresh Vercel deployment) and confirm the
TypeError is gone. If you changed the dependency, do not stop at npm ls abortcontroller-polyfill — it still lists the package for web3@1.8.2 and later, as shown
above. Check the version of whichever package actually requires it instead (npm ls web3-providers-http, for web3) against the version you confirmed is guarded. If you changed
the Node.js version instead, node -v in the same
environment that runs the build should report 18 or higher; Next's own polyfill file skips
entirely once global.fetch already exists, so there is nothing left for any dependency's
assignment to collide with.
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.
If it's not web3: finding the real culprit#
web3 is only the dependency the earliest reporters in that thread happened to share; the
mechanism has nothing to do with Ethereum libraries specifically. Anything that loads
abortcontroller-polyfill's dist/polyfill-patch-fetch entry point unconditionally — directly,
or several layers down a dependency tree, the way web3 below 1.8.2 did — trips the same
getter on an older Next.js running on Node.js below 18. npm ls abortcontroller-polyfill (step 1
above) is the fastest way to find the actual chain in your own project rather than assuming it is
whichever package this article happened to use as its example.
Switching to Node.js 18 does not always finish the job on the first try, either: in the same
GitHub thread, a developer who moved to Node.js 18 while on web3@1.8.0 hit a separate crash,
TypeError: callback is not a function inside web3-providers-http. He attributed it to
@loopring-web/web3-provider, a wrapper he suspected carried "a very old version of web3"; using
web3@1.8.1 directly on Node.js 18, instead of through that wrapper, resolved it for him. If
your error changes shape after a fix, read the new trace before assuming it is a continuation of
the same bug — it may not be.
It is also worth knowing which installs this clash cannot reach. node-polyfill-fetch.js is
confirmed absent from the published next@14.0.0 package, whose own engines.node field already
requires >=18.17.0 — a version that always has native fetch. This error belongs to Next.js
13.x installs (the ones that still ship the file) actually running on a Node.js below the
versions this reproduction confirmed safe (18.20.8 and below, down to 16.20.2); it is not a
current-Next.js problem, and not a Next.js 12.x one either — 12.x's own
node-polyfill-fetch.js assigns global.Request directly, without the getter-only
Object.defineProperties 13.x uses, so it never produces this clash. Two related build-only
failures worth ruling out
before assuming this is your cause: a native SWC binary missing for the platform that actually
runs the build, and a Docker Compose service name that
resolves in development but not inside the production build
— both produce a Next.js build that fails in one build environment but not another, for reasons
that have nothing to do with fetch polyfills.
Further reading: CORS Not Working on Vercel? Fix the vercel.json Headers.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix Next.js missing .next/BUILD_ID at server startup
Separate an absent build from the wrong application root, a mismatched output folder and development artifacts, using the file the production server checks.
CORS Not Working on Vercel? Fix the vercel.json Headers
Why a CORS header that passes on localhost can still get blocked once Vercel routes the request, and the one-layer fix Vercel documents for it.
Fix next-auth /api/auth/error in production: NO_SECRET
Reproduced on next-auth 4.24.15 and Next.js 15.5.28: why sign-in works on localhost but fails after deploy, what a 404 versus a 500 at /api/auth/error tells you, and the env change that fixes it.


