Next.js 12生产环境getServerSideProps报错:cookies不可迭代
问题描述
生产环境部署的Next.js 12 + React 16应用中,服务端组件本地运行正常,但生产环境突然抛出以下错误:
error: HttpError: cookies is not iterable at handleError (/usr/src/.next/server/chunks/6830.js:163:11) at sendRequest (/usr/src/.next/server/chunks/6830.js:214:16) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async getServerSideProps (/usr/src/.next/server/pages/addetail/[id].js:94:32) at async Object.renderToHTML (/usr/src/node_modules/next/dist/server/render.js:508:20) at async doRender (/usr/src/node_modules/next/dist/server/base-server.js:669:38) at async cacheEntry.responseCache.get.isManualRevalidate.isManualRevalidate (/usr/src/node_modules/next/dist/server/base-server.js:778:28) at async /usr/src/node_modules/next/dist/server/response-cache/index.js:80:36 { status: 500, body: undefined }
对应的服务端组件代码:
import React from 'react'; import Error from 'next/error'; import AdDetail from '../../addetail'; import { getAdDetailSSR } from '../../../components/http/fetch'; export async function getServerSideProps(props) { try { const serverResponse = await getAdDetailSSR(props.query.id); return { props: { serverResponse } }; } catch (err) { const errorCode = err.status; if (errorCode === 404) { return { notFound: true }; } return { props: { errorCode } }; } } const AdDetailRout = ({ errorCode, serverResponse }) => { if (errorCode) { return <Error statusCode={errorCode} />; } return <AdDetail res={serverResponse} />; }; export default AdDetailRout;
更新:所有服务端组件生产环境均无法正常工作,推测问题出在请求处理文件中,代码如下:
import { loadFromLocalStorage, saveToLocalStorage } from './localStorage'; const DefaultHttpOptions = { credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Cache: 'no-cache', Cookie: 'csrftoken=SOMETHING', }, }; class HttpError extends Error { constructor(message = '', status, body, ...args) { super(...args); this.message = message; this.status = status; this.body = body; } } const buildUrlWithParams = (url, params) => { const newURL = new URL(url); Object.keys(params).forEach(key => { newURL.searchParams.append(key, params[key]); }); }; const handleError = error => { throw new HttpError(error.message, error.status, error.body); }; const handleSuccess = response => { const contentType = response.headers.get('content-type'); const valueResponseMap = { 'application/json': 'json', 'image/jpg': 'blob', }; const bodyPromise = response[valueResponseMap[contentType] || 'text'](); return bodyPromise.then(body => { if (!response.ok) { return handleError({ message: body.message, status: response.status, body, }); } return body; }); }; const sendRequest = async (url, method, options) => { const { params, notSetToken, ...optionsRest } = options; const newUrl = params ? buildUrlWithParams(params) : url; const authUser = loadFromLocalStorage('user-auth'); if (authUser) { DefaultHttpOptions.headers.Authorization = `Bearer ${authUser.token}`; } if (notSetToken) { delete DefaultHttpOptions.headers.Authorization; } const httpOptions = { method, ...optionsRest, ...DefaultHttpOptions, headers: { ...DefaultHttpOptions.headers, ...optionsRest.headers }, }; try { const response = await fetch(`${newUrl}`, httpOptions); for (let entry of response.headers.entries()) { if (entry[0] === 'access-token' && authUser.token !== entry[1]) { saveToLocalStorage( { ...authUser, token: entry[1], }, 'user-auth' ); } } return handleSuccess(response, optionsRest); } catch (error) { return handleError(error); } }; const Apiurl ='MYAPIROUT'; const HttpRequestSSR = { get(url, options) { return sendRequest(Apiurl + url, 'GET', options); }, post(url, options) { return sendRequest(Apiurl + url, 'POST', options); }, put(url, options) { return sendRequest(Apiurl + url, 'PUT', options); }, delete(url, options) { return sendRequest(Apiurl + url, 'DELETE', options); }, }; export default HttpRequestSSR;
需要明确报错含义、产生原因及解决方法。
问题分析与解决
报错含义
cookies is not iterable 表示代码尝试遍历一个不可迭代的cookies对象——通常是预期cookies为数组或可遍历集合,但实际拿到了undefined、null或非可遍历类型,最终触发错误。
产生原因
结合代码和报错栈,问题根源在服务端执行的请求逻辑中,具体有3个核心问题:
- 服务端无法访问
localStorage:loadFromLocalStorage/saveToLocalStorage调用了浏览器专属的localStorage,但getServerSideProps在Node.js环境执行,服务端没有这个对象,直接调用会抛出错误,被handleError包装后呈现为cookies is not iterable(错误信息在传递中被改写,或依赖库处理localStorage缺失时触发了cookies相关异常)。 buildUrlWithParams无返回值:该函数创建URL对象并添加参数,但未返回拼接后的URL字符串,导致有params时newUrl为undefined,后续fetch调用失败触发错误。- 全局修改
DefaultHttpOptions引发竞态:sendRequest中直接修改全局的请求头配置,并发请求下会导致请求头混乱,触发不可预期的错误。
解决方法
1. 替换服务端的localStorage逻辑
服务端无法读取浏览器localStorage,需通过getServerSideProps的req参数从请求头/cookies中获取认证信息:
- 修改
getServerSideProps,传递请求中的token:
export async function getServerSideProps({ query, req }) { try { // 从请求头或cookies中读取token,示例从headers获取 const authToken = req.headers.authorization; const serverResponse = await getAdDetailSSR(query.id, authToken); return { props: { serverResponse } }; } catch (err) { const errorCode = err.status; if (errorCode === 404) { return { notFound: true }; } return { props: { errorCode } }; } }
- 修改请求工具,去掉服务端的
localStorage调用,改为接收外部传入的token:
// 修改sendRequest,接收外部传入的authToken const sendRequest = async (url, method, options = {}, authToken) => { const { params, notSetToken, ...optionsRest } = options; // 修复URL拼接逻辑的返回值问题 let newUrl = url; if (params) { newUrl = buildUrlWithParams(url, params); } // 创建独立的请求头,避免修改全局配置 const httpOptions = { method, ...optionsRest, ...DefaultHttpOptions, headers: { ...DefaultHttpOptions.headers, ...optionsRest.headers }, }; // 根据传入的token设置Authorization头 if (authToken && !notSetToken) { httpOptions.headers.Authorization = authToken; } else if (notSetToken) { delete httpOptions.headers.Authorization; } try { const response = await fetch(newUrl, httpOptions); // 服务端不要调用saveToLocalStorage,如需更新token,返回给前端在客户端处理 return handleSuccess(response); } catch (error) { handleError(error); } }; // 修改HttpRequestSSR的方法,支持传入authToken const HttpRequestSSR = { get(url, options, authToken) { return sendRequest(Apiurl + url, 'GET', options, authToken); }, post(url, options, authToken) { return sendRequest(Apiurl + url, 'POST', options, authToken); }, put(url, options, authToken) { return sendRequest(Apiurl + url, 'PUT', options, authToken); }, delete(url, options, authToken) { return sendRequest(Apiurl + url, 'DELETE', options, authToken); }, };
2. 修复buildUrlWithParams返回值
const buildUrlWithParams = (url, params) => { const newURL = new URL(url); Object.keys(params).forEach(key => { newURL.searchParams.append(key, params[key]); }); // 返回拼接后的完整URL字符串 return newURL.toString(); };
3. 优化错误处理
保留原始错误信息,便于生产环境排查:
const handleError = error => { // 生产环境可将错误输出到日志服务 console.error('Request failed:', error); throw new HttpError(error.message || 'Request error', error.status || 500, error.body); };
内容的提问来源于stack exchange,提问作者user19963522
相关产品推荐
相关产品推荐

