diff --git a/.eslintrc.json b/.eslintrc.json
index d20dd2b1453628b1634c5dc2764bd5fd16460ee1..e67d0fa650b059ec2662f3ecc08a9dc4033390c5 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -2,7 +2,7 @@
   "root": true,
   "env": {
     "node": true,
-    "es2021": true
+    "es2022": true
   },
   "extends": [
     "standard"
diff --git a/integration/.eslintrc b/integration/.eslintrc
index 4016c2b5b0a5135ae63ef8e0c471c9bc61d77935..f06fe47ac2cc10004d7c7201256fd9162a172816 100644
--- a/integration/.eslintrc
+++ b/integration/.eslintrc
@@ -2,5 +2,8 @@
   "extends": [
     "plugin:mocha/recommended"
   ],
-  "plugins": ["mocha"]
+  "plugins": ["mocha"],
+  "globals": {
+    "Response": true
+  }
 }
diff --git a/integration/caching_test.js b/integration/caching_test.js
index 66f1fa62228f59f3a44d68a0a82debbb7f700d76..b3915a8c7e6f8c5d12f13fdb01d38c7585d494c4 100644
--- a/integration/caching_test.js
+++ b/integration/caching_test.js
@@ -1,7 +1,6 @@
 import request from 'supertest'
 import { expect } from 'chai'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch } from '../spec/util.js'
-import { Response } from 'node-fetch'
 import { client } from '../src/redis.js'
 import * as td from 'testdouble'
 import { getRedisKey } from '../src/util.js'
diff --git a/integration/config_test.js b/integration/config_test.js
index b5f6d271c0f3f16f8e392f79b7f220067e93d64c..87ed9dc24e96107034f7f9276fc056eee1fab8fa 100644
--- a/integration/config_test.js
+++ b/integration/config_test.js
@@ -1,7 +1,6 @@
 import request from 'supertest'
 import { expect } from 'chai'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch } from '../spec/util.js'
-import { Response } from 'node-fetch'
 import { client, closeQueue, getQueues, pubClient } from '../src/redis.js'
 import * as td from 'testdouble'
 import { getRedisKey } from '../src/util.js'
diff --git a/integration/update-version_test.js b/integration/update-version_test.js
index 2a540e633e2ab77b91eca6fad9229a11ba4a0181..82475fbadf53e6b474ce8988a77b92c289ab9abe 100644
--- a/integration/update-version_test.js
+++ b/integration/update-version_test.js
@@ -1,7 +1,6 @@
 import request from 'supertest'
 import { expect } from 'chai'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch } from '../spec/util.js'
-import { Response } from 'node-fetch'
 import { client, closeQueue, getQueue, getQueues, pubClient } from '../src/redis.js'
 import * as td from 'testdouble'
 import { getRedisKey } from '../src/util.js'
diff --git a/package.json b/package.json
index 9a44fd825528781c56de2fd9baa5ed8638f68483..2e3f0939e635acbd6412fa0b62b5b0c92d4e0bf5 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,6 @@
     "http-errors": "^2.0.0",
     "ioredis": "^5.2.2",
     "js-yaml": "^4.0.0",
-    "node-fetch": "^3.2.10",
     "swagger-ui-express": "^4.5.0"
   },
   "devDependencies": {
diff --git a/spec/.eslintrc b/spec/.eslintrc
index 4016c2b5b0a5135ae63ef8e0c471c9bc61d77935..f06fe47ac2cc10004d7c7201256fd9162a172816 100644
--- a/spec/.eslintrc
+++ b/spec/.eslintrc
@@ -2,5 +2,8 @@
   "extends": [
     "plugin:mocha/recommended"
   ],
-  "plugins": ["mocha"]
+  "plugins": ["mocha"],
+  "globals": {
+    "Response": true
+  }
 }
diff --git a/spec/file-depencies_test.js b/spec/file-depencies_test.js
index b65d4d832ef44e18dca211e888f3a5401d61273b..64a77c3ae80afbb22f6ab98fd02597b310d9de06 100644
--- a/spec/file-depencies_test.js
+++ b/spec/file-depencies_test.js
@@ -1,7 +1,6 @@
 import request from 'supertest'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch, mockRedis } from './util.js'
 import { expect } from 'chai'
-import { Response } from 'node-fetch'
 import * as td from 'testdouble'
 import RedisMock from 'ioredis-mock'
 
diff --git a/spec/file_caching_test.js b/spec/file_caching_test.js
index 6c6c0327ef8ee087911dc924ba89eff1e5e9e4bb..2037f3aa8a9c1c49abc1f2ab751ba98a31fd25a4 100644
--- a/spec/file_caching_test.js
+++ b/spec/file_caching_test.js
@@ -3,7 +3,6 @@ import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch, mockRedis }
 import fs from 'fs'
 import { expect } from 'chai'
 import * as td from 'testdouble'
-import { Response } from 'node-fetch'
 import RedisMock from 'ioredis-mock'
 import sinon from 'sinon'
 
diff --git a/spec/headers_test.js b/spec/headers_test.js
index 3f5aeef8dd1461f4c1aa16bed8a6f37e92872022..3b6b124cbc2c27f16287cae435ecd09e6e8a9256 100644
--- a/spec/headers_test.js
+++ b/spec/headers_test.js
@@ -1,7 +1,6 @@
 import request from 'supertest'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch, mockRedis } from './util.js'
 import { expect } from 'chai'
-import { Response } from 'node-fetch'
 import * as td from 'testdouble'
 import RedisMock from 'ioredis-mock'
 
diff --git a/spec/meta_test.js b/spec/meta_test.js
index cbf94fe16a3db1f5715eecb6303aa9e1560aeb10..5b57dd23dc8f86c490c96c4f93c065912e74d2f5 100644
--- a/spec/meta_test.js
+++ b/spec/meta_test.js
@@ -1,7 +1,6 @@
 import request from 'supertest'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch, mockRedis } from './util.js'
 import { expect } from 'chai'
-import { Response } from 'node-fetch'
 import * as td from 'testdouble'
 import RedisMock from 'ioredis-mock'
 
diff --git a/spec/redis_test.js b/spec/redis_test.js
index c9ce3e8a282334aff7638344fe10d6f1bfee6ac0..26da95c55a36e45716e6a07517c5b9ac29988204 100644
--- a/spec/redis_test.js
+++ b/spec/redis_test.js
@@ -2,7 +2,6 @@ import request from 'supertest'
 import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch } from './util.js'
 import { expect } from 'chai'
 import * as td from 'testdouble'
-import { Response } from 'node-fetch'
 import sinon from 'sinon'
 
 const sandbox = sinon.createSandbox()
diff --git a/spec/util.js b/spec/util.js
index a7419c640ed1b6e1779f07ac62f3607718d3015a..78d34a7bbe59eb975d71bc4200a0a48a5ec8d406 100644
--- a/spec/util.js
+++ b/spec/util.js
@@ -1,7 +1,6 @@
 import * as td from 'testdouble'
 import { register } from 'prom-client'
 import request from 'supertest'
-import { Response } from 'node-fetch'
 import RedisMock from 'ioredis-mock'
 
 export function generateSimpleViteManifest (mapping) {
@@ -25,7 +24,7 @@ export function mockConfig (obj = {}) {
 }
 
 export function mockFetch (servers = {}) {
-  td.replaceEsm('node-fetch', {}, async function ({ origin, pathname }) {
+  td.replace(global, 'fetch', async function ({ origin, pathname }) {
     const response = servers[origin]?.[pathname]
     if (response === undefined) return new Response('', { status: 404 })
     if (response instanceof Function) return response.apply(this, arguments)
diff --git a/src/files.js b/src/files.js
index 8644525ba464dbb05fdded3685910d4fd55b9518..7fe9bdb3f1dc2ee0902ba587745827fee275d973 100644
--- a/src/files.js
+++ b/src/files.js
@@ -1,4 +1,3 @@
-import fetch from 'node-fetch'
 import crypto from 'crypto'
 import { config } from './config.js'
 import { getRedisKey, isJSFile } from './util.js'
diff --git a/src/manifests.js b/src/manifests.js
index 7f6ed5da34922fa7336f886f5ff25bd50283c61a..5fbcac71c101583b1e1456916149108590c92deb 100644
--- a/src/manifests.js
+++ b/src/manifests.js
@@ -1,4 +1,3 @@
-import fetch from 'node-fetch'
 import { config } from './config.js'
 import { getRedisKey, viteManifestToDeps, viteToOxManifest } from './util.js'
 import { logger } from './logger.js'
diff --git a/src/meta.js b/src/meta.js
index 386c78b3cacae2a06decc1b9977871e68e120d67..d9d5f91769151478362899043b2624155d18665d 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -1,5 +1,4 @@
 import { config } from './config.js'
-import fetch from 'node-fetch'
 import * as cache from './cache.js'
 import { getRedisKey } from './util.js'
 
diff --git a/src/version.js b/src/version.js
index 6c78dc2fb9975e1080deff8ecc1b939cc73bca2f..24e67053e5fc26fe261bff9f15e17c458a7d58b3 100644
--- a/src/version.js
+++ b/src/version.js
@@ -1,4 +1,3 @@
-import fetch from 'node-fetch'
 import { config } from './config.js'
 import { getRedisKey, hash } from './util.js'
 import { logger } from './logger.js'
diff --git a/yarn.lock b/yarn.lock
index 8db6aa2c2ebda6379a56500a27733491e6855125..4d87a08f89253ee8cb18a9733fcdf9593245b0c4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -621,11 +621,6 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
-data-uri-to-buffer@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
-  integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
-
 dateformat@^4.6.3:
   version "4.6.3"
   resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5"
@@ -1183,14 +1178,6 @@ fengari@^0.1.4:
     sprintf-js "^1.1.1"
     tmp "^0.0.33"
 
-fetch-blob@^3.1.2, fetch-blob@^3.1.4:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
-  integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
-  dependencies:
-    node-domexception "^1.0.0"
-    web-streams-polyfill "^3.0.3"
-
 file-entry-cache@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -1253,13 +1240,6 @@ form-data@^4.0.0:
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
-formdata-polyfill@^4.0.10:
-  version "4.0.10"
-  resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
-  integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
-  dependencies:
-    fetch-blob "^3.1.2"
-
 formidable@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff"
@@ -2084,20 +2064,6 @@ nise@^5.1.1:
     just-extend "^4.0.2"
     path-to-regexp "^1.7.0"
 
-node-domexception@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
-  integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
-
-node-fetch@^3.2.10:
-  version "3.2.10"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8"
-  integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
-  dependencies:
-    data-uri-to-buffer "^4.0.0"
-    fetch-blob "^3.1.4"
-    formdata-polyfill "^4.0.10"
-
 node-gyp-build-optional-packages@5.0.3:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17"
@@ -3116,11 +3082,6 @@ vary@~1.1.2:
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
 
-web-streams-polyfill@^3.0.3:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
-  integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
-
 which-boxed-primitive@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"