NextJS+MongoDB构建Codepen类应用时触发TypeError: Cannot read properties of undefined (reading 'updatedRecord')问题求助
Cannot read properties of undefined (reading 'updatedRecord') 错误 这个错误的核心原因很明确:你在解构 API 响应时,尝试读取 data.updatedRecord,但data是undefined。咱们一步步拆解问题,找到解决办法:
1. 先确认 API 实际返回的内容
旧教程的后端可能返回{ data: { updatedRecord, newRecordId } }这样的结构,但现在你的后端(不管是自己写的还是教程项目的)可能返回格式变了,或者请求本身就失败了。
先在save函数里加一行日志,看看真实的响应结构:
const save = async () => { setSaving(true); try { // ... 你的请求配置代码 ... const response = await fetch(`../api/pens/${id}`, requestOptions); // 新增:打印完整响应结果 const rawResponse = await response.json(); console.log("API 返回内容:", rawResponse); // 原来的解构代码先注释掉,先看日志 // const { data: { updatedRecord, newRecordId }, } = rawResponse; // ... 后续代码 ... } catch (err) { console.error("保存出错:", err); setSaving(false); } };
打开浏览器开发者工具的「控制台」和「网络」标签,点击保存按钮后:
- 如果看到返回的是
{ updatedRecord: ..., newRecordId: ... }(没有外层data),那直接把解构改成const { updatedRecord, newRecordId } = rawResponse就行; - 如果返回的是错误信息(比如
{ error: "数据库连接失败" }),那说明后端逻辑有问题,需要去排查 API 路由的代码。
2. 检查请求方法和路由是否匹配
你代码里的请求方法逻辑有点反直觉:
method: !id ? "PUT" : "POST"
通常新建资源用POST,更新用PUT/PATCH,旧教程可能当时这么写没问题,但新版本的后端框架可能对方法的校验更严格。建议改成:
method: !id ? "POST" : "PUT"
另外,新建 pen 时id是undefined,这时候请求路径../api/pens/${id}会变成../api/pens/undefined,这大概率不是后端期望的路径。新建应该请求../api/pens,更新才带id:
const url = !id ? "../api/pens" : `../api/pens/${id}`; const response = await fetch(url, requestOptions);
3. 给请求加上错误处理
如果后端返回了错误状态码(比如400、500),fetch不会自动抛出错误,会继续执行后续的解构逻辑,导致读取undefined的属性。给请求加上状态校验:
const response = await fetch(url, requestOptions); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `请求失败,状态码:${response.status}`); }
4. 排查 Next.js API 路由的版本差异
几年前的 Next.js 用的是 Pages Router,API 路由放在pages/api目录下;如果你现在用的是 Next.js 13+ 的 App Router,API 路由的写法完全不同(放在app/api目录,导出POST/PUT等方法)。
如果教程用的是 Pages Router,而你用了 App Router,那需要把 API 路由改成对应版本的写法:
- Pages Router 示例(
pages/api/pens/[id].js):
export default async function handler(req, res) { if (req.method === "POST") { // 处理更新逻辑 } else if (req.method === "PUT") { // 处理新建逻辑 } else { res.status(405).json({ error error: "不支持的请求方法" }); } }
- App Router 示例(
app/api/pens/[id]/route.js):
export async function POST(request) { // 处理更新逻辑 } export async function PUT(request) { // 处理新建逻辑 }
最后调整后的完整示例
把上面的修改整合起来,你的save函数可以改成这样:
const save = async () => { setSaving(true); try { const requestOptions = { method: !id ? "POST" : "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ html: htmlValue, css: cssValue, js: jsValue, id }), }; const url = !id ? "../api/pens" : `../api/pens/${id}`; const response = await fetch(url, requestOptions); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `请求失败,状态码:${response.status}`); } const rawResponse = await response.json(); console.log("API 返回内容:", rawResponse); // 根据实际返回结构调整解构 const { updatedRecord, newRecordId } = rawResponse.data || rawResponse; setSaving(false); if (!updatedRecord) { await router.push(`?id=${newRecordId}`); } } catch (error) { setSaving(false); console.error("保存失败:", error); // 这里可以加个用户提示,比如用toast告知保存出错 } };
内容的提问来源于stack exchange,提问作者Pepelius

