Next.js服务端组件传参至客户端遇toJSON对象不支持警告问题
问题背景
我在Next.js App Router中用Sequelize从MySQL取数据,getTransactions方法返回结构如下:
const { rows, count } = await Transaction.findAndCountAll({ limit, offset, where: whereClause, order: [['created_at', 'DESC']], }); return { items: rows, totalItems: count, totalPages: Math.ceil(count / limit), currentBalance, };
把这份数据传给客户端组件时,控制台弹出警告:
Warning: Only plain objects can be passed to Client Components from Server Components. Objects with toJSON methods are not supported. Convert it manually to a simple value before passing it to props.
服务端组件代码:
const data = await getTransactions({ page, limit, query: search }); return ( <TransactionsTable response={data} /> );
客户端组件代码:
const TransactionsTable = ({ response }: { response : PaginatedTransactions | null }) => { // TSX 渲染逻辑 }
我通过序列化+反序列化,手动把created_at和updated_at转回Date类型,解决了警告问题,处理代码如下:
const data = await getTransactions({ page, limit, query: search }); const serialize = JSON.stringify(data); const deserialize = JSON.parse(serialize, (key, value) => { if (key === "created_at" || key === "updated_at") { return new Date(value); } return value; }) as PaginatedTransactions | null; <TransactionsTable response={deserialize} />
但我不明白为什么必须做这些额外操作?
原因解析
Sequelize的findAndCountAll返回的rows数组里,每一条记录都不是普通JS对象,而是Sequelize封装的Model实例对象。这些实例自带toJSON()等自定义方法,还有Sequelize内部的属性和逻辑,属于「带额外能力的复杂对象」,不符合Next.js对跨组件传递props的要求。
Next.js App Router在服务端组件向客户端组件传递数据时,本质是把数据序列化为JSON格式在服务端和客户端之间传输。为了保证传输的可靠性和性能,Next.js明确限制:只能传递纯对象、字符串、数字这类「可直接序列化的简单值」,不能传递带自定义方法或内部状态的复杂对象——这类对象无法被正确序列化,而且客户端也不需要服务端Model实例的额外方法。
你做的序列化+反序列化操作,本质是把Sequelize的复杂实例转成了纯对象:
JSON.stringify(data)会自动调用Model实例的toJSON()方法,剥离掉Sequelize的内部属性和方法,只保留数据字段;JSON.parse()把JSON字符串转回纯JS对象,同时你手动把被序列化转成字符串的日期字段还原成了Date类型(JSON本身不支持Date类型,序列化时会自动转成字符串)。
处理后传给客户端的就是符合要求的纯对象,所以警告消失了。
更简洁的替代方案
其实不用手动做JSON序列化/反序列化,Sequelize自带了把实例转成纯对象的方法:
- 单个实例用
instance.get({ plain: true }); - 批量处理数组用
rows.map(row => row.get({ plain: true }))。
修改getTransactions方法即可:
const { rows, count } = await Transaction.findAndCountAll({ limit, offset, where: whereClause, order: [['created_at', 'DESC']], }); // 把Sequelize实例批量转成纯对象 const plainItems = rows.map(row => row.get({ plain: true })); return { items: plainItems, totalItems: count, totalPages: Math.ceil(count / limit), currentBalance, };
这样返回的items就是纯对象数组,直接传给客户端组件不会触发警告,还能保留日期字段的Date类型,省去了手动序列化反序列化的步骤。
内容的提问来源于stack exchange,提问作者Ali Hassan

