You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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个核心问题:

  1. 服务端无法访问localStorage:loadFromLocalStorage/saveToLocalStorage调用了浏览器专属的localStorage,但getServerSideProps在Node.js环境执行,服务端没有这个对象,直接调用会抛出错误,被handleError包装后呈现为cookies is not iterable(错误信息在传递中被改写,或依赖库处理localStorage缺失时触发了cookies相关异常)。
  2. buildUrlWithParams无返回值:该函数创建URL对象并添加参数,但未返回拼接后的URL字符串,导致有params时newUrl为undefined,后续fetch调用失败触发错误。
  3. 全局修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 15:15:27