NodeJS16升级至18/20后NextJS+Apollo触发Zlib头部校验错误
Node.js 升级至18/20后 Apollo Client 出现 Z_DATA_ERROR 错误
问题背景
代码在Node.js 16.20.0环境下运行正常,但升级到Node.js 18(或20)版本后,在getServerSideProps中调用apolloClient.query时触发网络错误。登录页可正常从API端点获取Cookie,但后续服务端请求报错。
错误信息
[http] [Network error]: TypeError: terminated [http] Error: incorrect header check [http] at Zlib.zlibOnError [as onerror] (node:zlib:189:17) { [http] errno: -3, [http] code: 'Z_DATA_ERROR' [http] }
关键代码(apollo.ts)
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined; const errorLink = onError(({ graphQLErrors, networkError, response }) => {...}); const authLink = setContext((_, { headers }) => { const AUTHENTICATION = toBase64(CREDENTIALS); return { headers: { ...headers, authorization: `Basic ${AUTHENTICATION}` }, }; }); function createApolloClient(headers: IncomingHttpHeaders | null = null) { // isomorphic fetch for passing the cookies along with each GraphQL request const enhancedFetch = (url: RequestInfo, init: RequestInit) => { if (headers) { init.headers = { ...init.headers, cookie: headers.cookie ?? '', }; } return fetch(url, init); }; return new ApolloClient({ ssrMode: typeof window === 'undefined', defaultOptions: { query: { errorPolicy: 'all', }, }, link: from([ errorLink, authLink, createUploadLink({ uri: `https://${process.env.NEXT_PUBLIC_API_ENDPOINT}/graphql`, credentials: 'include', fetch: enhancedFetch, // Make sure that CORS and cookies work // fetch: typeof window !== 'undefined' ? enhancedFetch : undefined, }), ]), cache: new InMemoryCache({...}), connectToDevTools: true, }); } type InitialState = NormalizedCacheObject | undefined; interface IInitializeApollo { headers?: IncomingHttpHeaders | null; initialState?: InitialState | null; } export function initializeApollo( { headers, initialState }: IInitializeApollo = { headers: null, initialState: null, } ): ApolloClient<NormalizedCacheObject> { const _apolloClient = apolloClient ?? createApolloClient(headers); // If your page has Next.js data fetching methods that use Apollo Client, the initial state // get hydrated here if (initialState) { // Get existing cache, loaded during client side data fetching const existingCache = _apolloClient.extract(); // Merge the existing cache into data passed from getStaticProps/getServerSideProps const data = merge(initialState, existingCache, { // combine arrays using object equality (like in sets) arrayMerge: (destinationArray, sourceArray) => [ ...sourceArray, ...destinationArray.filter((d) => sourceArray.every((s) => !isEqual(d, s))), ], }); // Restore the cache with the merged data _apolloClient.cache.restore(data); } // For SSG and SSR always create a new Apollo Client if (typeof window === 'undefined') return _apolloClient; // Create the Apollo Client once in the client if (!apolloClient) apolloClient = _apolloClient; return _apolloClient; } export function useApollo(initialState = {}): ApolloClient<NormalizedCacheObject> { const store = useMemo(() => initializeApollo({ headers: null, initialState }), [initialState]); return store; }
环境信息
Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 22.5.0: Mon Apr 24 20:51:50 PDT 2023; root:xnu-8796.121.2~5/RELEASE_X86_64 Binaries: Node: 18.16.0 npm: 9.5.1 Yarn: N/A pnpm: N/A Relevant packages: next: 13.4.5 eslint-config-next: 13.4.5 react: 18.2.0 react-dom: 18.2.0 typescript: 5.1.3
解决方案
1. 禁用响应压缩
Node.js 18+的fetch实现(基于undici)对压缩数据的头部校验更严格,若API返回的压缩数据存在校验问题会触发错误。可在请求头中明确禁用压缩:
const enhancedFetch = (url: RequestInfo, init: RequestInit) => { if (headers) { init.headers = { ...init.headers, cookie: headers.cookie ?? '', 'Accept-Encoding': 'identity' // 禁用压缩 }; } return fetch(url, init); };
2. 修复SSR实例复用问题
当前逻辑在服务端可能复用已有Apollo实例,导致请求头混乱。修改逻辑强制服务端每次创建新实例:
export function initializeApollo( { headers, initialState }: IInitializeApollo = { headers: null, initialState: null, } ): ApolloClient<NormalizedCacheObject> { // 服务端强制创建新实例,避免请求头污染 const _apolloClient = typeof window === 'undefined' ? createApolloClient(headers) : apolloClient ?? createApolloClient(headers); // 缓存合并逻辑保持不变 if (initialState) { const existingCache = _apolloClient.extract(); const data = merge(initialState, existingCache, { arrayMerge: (destinationArray, sourceArray) => [ ...sourceArray, ...destinationArray.filter((d) => sourceArray.every((s) => !isEqual(d, s))), ], }); _apolloClient.cache.restore(data); } if (!apolloClient && typeof window !== 'undefined') apolloClient = _apolloClient; return _apolloClient; }
3. 更新依赖版本
确保apollo-client、apollo-upload-client(createUploadLink来源)等依赖为最新版本,新版本通常已适配Node.js 18+的fetch实现。
4. 验证Cookie格式
在enhancedFetch中打印headers.cookie内容,确认不存在无效字符或格式错误,避免因Cookie问题导致响应处理异常。
内容的提问来源于stack exchange,提问作者rkrd
相关产品推荐
相关产品推荐

