Next.js 15.1.7中调用依赖@basisai/remote-zip的Server Action时出现XMLHttpRequest未定义错误
问题场景
我在Next.js项目中通过Server Action调用@basisai/remote-zip库读取远程ZIP文件内容时遇到了服务器端错误。具体代码结构如下:
前端客户端组件
'use client'; import Image from "next/image"; import { doSomething } from "./serveractions/do-something"; export default function Home() { const handleClick = async () => { await doSomething() } return ( <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> <button onClick={handleClick}>Press me</button> {/* 其余组件代码 */} </div> ); }
Server Action 代码
'use server'; import { RemoteZipPointer } from "@basisai/remote-zip"; export const doSomething = async() => { const url = new URL("https://github.com/unidoc/unipdf/archive/refs/tags/v3.66.0.zip"); const remoteZip = await new RemoteZipPointer({ url }).populate(); const files = remoteZip.files(); files.forEach(file => console.log(file.filename)); }
点击按钮后触发服务器端错误,提示XMLHttpRequest is not defined,但相同代码在纯Node.js(Bun)环境下可以正常运行。我尝试过替换全局XMLHttpRequest为xmlhttprequest、xhr2或node-fetch,但均抛出TypeError: i.arrayBuffer is not a function的新错误。
问题原因
@basisai/remote-zip库底层依赖浏览器环境的XMLHttpRequest API,但Next.js的Server Action运行在Node.js服务器环境中,Node.js本身并没有内置这个浏览器专属的API,这就导致了未定义的错误。而你之前尝试的替换方案,其提供的XMLHttpRequest实现不完整,缺少库所需的arrayBuffer等标准方法,因此引发了新的类型错误。
解决方案
我们可以通过为Node.js环境提供完全符合标准的XMLHttpRequest实现,或者适配库的请求逻辑来解决这个问题,以下是两种可行方案:
方案1:使用undici提供兼容的XMLHttpRequest(推荐)
undici是Node.js官方推荐的高性能HTTP客户端,它提供了与浏览器标准完全对齐的XMLHttpRequest实现,完美支持arrayBuffer等方法。
- 首先安装依赖:
npm install undici # 或使用 yarn add undici / pnpm add undici
- 在Server Action代码顶部注入全局兼容实现:
'use server'; // 为Node.js环境注入标准兼容的XMLHttpRequest import { XMLHttpRequest } from 'undici'; globalThis.XMLHttpRequest = XMLHttpRequest; import { RemoteZipPointer } from "@basisai/remote-zip"; export const doSomething = async() => { const url = new URL("https://github.com/unidoc/unipdf/archive/refs/tags/v3.66.0.zip"); const remoteZip = await new RemoteZipPointer({ url }).populate(); const files = remoteZip.files(); files.forEach(file => console.log(file.filename)); }
方案2:自定义请求适配器(需库支持)
如果@basisai/remote-zip文档中提到允许自定义请求逻辑,可以直接用Node.js 18+内置的fetch API替代默认的XMLHttpRequest:
注意:该方案仅当库开放了自定义请求器的配置项时可用,若库未提供此能力则无法使用。
'use server'; import { RemoteZipPointer } from "@basisai/remote-zip"; export const doSomething = async() => { const url = new URL("https://github.com/unidoc/unipdf/archive/refs/tags/v3.66.0.zip"); // 假设库支持通过request参数自定义请求逻辑 const remoteZip = await new RemoteZipPointer({ url, request: async (reqConfig) => { const response = await fetch(url, { method: reqConfig.method || 'GET', headers: reqConfig.headers, }); // 返回库所需的响应结构 return { arrayBuffer: () => response.arrayBuffer(), status: response.status, getResponseHeader: (name) => response.headers.get(name) }; } }).populate(); const files = remoteZip.files(); files.forEach(file => console.log(file.filename)); }
验证
修改完成后重启Next.js项目,点击按钮即可正常读取远程ZIP文件的文件列表,不会再出现XMLHttpRequest is not defined的错误。
备注:内容来源于stack exchange,提问作者Dima Vavilov

