Next.js 14中cookies().set()触发页面刷新及TRPC请求异常问题
1. Next.js 14中调用cookies().set()是否会触发整页刷新?
在图表组件切换时间间隔时,原本仅重新获取数据无异常,但现在触发整页刷新,导致同路由内的高负载组件也随之重载。官方文档仅说明:
Using it in a layout or page will opt a route into dynamic rendering at request time.
但这不应该意味着调用该方法会触发整页刷新,对吗?
2. TRPC请求失败问题
网络面板中所有后端请求均出现获取响应失败的情况,带有cookies()调用的组件的请求负载包含设置的cookie值或Next.js生成的乱码内容,示例如下:
1: "tkJNa2edO1ReSBFeTtF3/ylWv+2A46cEY5LLkcOCkYgMnfkWeX/SMd+/CCIPBH4W9IettDZ1H0IM9xHzjjKJ4w28FevcMDLnMkHF0H7TymCoxFH04w==" 2_$ACTION_REF_1: 2_$ACTION_1:1: ["$@2"] 2_$ACTION_1:0: {"id":"8ad85c6f04f4b5912851a1a1b7731332e1b2753c","bound":"$@1"} 2_$ACTION_1:2: "tWfv6x4N8OQpm2utvGMwGwQAHv8WRKnPZYXCJEXaI224Bve7D+IVjbkA9F+v9u5hdU2xmwNmx8usNLY5DsewZqmpDRXD1X7vzsM33CUZSs8wsg9DVA==" 0: ["$@1","$K2"]
相关代码
TradingCharts组件
export async function TradingCharts({ symbol }: D3ChartProps) { const interval = (cookies().get('interval')?.value ?? '1d') as KlineInterval; const candlesticksPromise = client.binance.candlestick.query({ symbol, interval, }); const lastTradePromise = client.binance.lastTrade.query({ symbol }); const [candlesticks, recentTrade] = await Promise.all([candlesticksPromise, lastTradePromise]); const handleIntervalChange = async (interval: KlineInterval) => { 'use server'; cookies().set('interval', interval, { maxAge: 0 }); }; return ( <section className={styles.container}> <IntervalButtons changeInterval={handleIntervalChange} currentInterval={interval} /> <CandlestickChart {...{ symbol, candlesticks, interval, recentTrade: parseFloat(recentTrade ?? '0') }} key={interval} /> </section> ); }
TRPC客户端初始化代码
export const client = createTRPCProxyClient<AppRouter>({ links: [ splitLink({ condition: (op) => op.type === 'subscription', true: wsLink({ client: createWSClient({ url: 'ws://localhost:7000', }), }), false: httpBatchLink({ url: 'http://localhost:7000', fetch(url, options) { return fetch(url, { ...options, credentials: 'include', cache: 'no-store', }); }, }), }), ], });
关于cookies().set()触发整页刷新的问题
你当前的handleIntervalChange是Server Action(因为标注了'use server'),Next.js 13+中Server Action默认执行后会触发整页重新渲染——这是Server Action的特性,和cookies().set()本身无关。
要避免整页刷新,推荐两种方案:
改用客户端操作Cookie:将按钮逻辑抽离为客户端组件,用
document.cookie设置Cookie后,调用router.refresh()局部刷新服务端组件(不会触发整页重载)。示例:'use client'; import { useRouter } from 'next/navigation'; export function IntervalButtonsClient({ currentInterval }: { currentInterval: KlineInterval }) { const router = useRouter(); const handleIntervalChange = (interval: KlineInterval) => { document.cookie = `interval=${interval}; path=/; max-age=0`; router.refresh(); }; return <IntervalButtons changeInterval={handleIntervalChange} currentInterval={currentInterval} />; }之后在
TradingCharts组件中替换为这个客户端组件即可。配置Server Action无刷新更新:使用Next.js的
experimental_useOptimistic配合状态管理实现无刷新,但复杂度较高,不如第一种方案直接。
关于TRPC请求失败的问题
你看到的乱码负载是Next.js Server Action的内部序列化格式,原因是TradingCharts作为服务端组件,直接调用了客户端TRPC实例——TRPC的httpBatchLink默认在客户端环境发送请求,在服务端组件中调用会被转发为Server Action的内部请求,导致格式异常。
解决方法:
服务端直接调用TRPC Procedure:如果TRPC路由和前端同项目,可在服务端组件中直接调用后端的Procedure函数,无需通过客户端实例。示例:
import { appRouter } from '@/server/api/root'; import { createTRPCContext } from '@/server/api/trpc'; // 在TradingCharts组件中 const ctx = await createTRPCContext({}); const candlesticks = await appRouter.binance.candlestick.query({ symbol, interval }, ctx); const recentTrade = await appRouter.binance.lastTrade.query({ symbol }, ctx);将TRPC调用移到客户端组件:给
TradingCharts添加'use client'标记转为客户端组件,此时TRPC的httpBatchLink会正常发送客户端请求,不会被Server Action干扰,同时配合客户端Cookie操作避免整页刷新。
内容的提问来源于stack exchange,提问作者seven

