96SEO 2026-09-07 09:36 1
当模型侧快速推送流式数据时页面完全卡死无法操作。通过 Performance Monitor 工具分析结果如下:

在找到正确解法前,我们尝试了所有常规 React 性能调整手段。全部无效:
| 方案 | 结果 | 失败原因 |
|---|---|---|
useDeferredValue | CPU 不降,但每秒仍有 + 次 setMessages → React 调度 + 组件执行 + DOM diff 照常跑。 | |
useThrottle | CPU 不降。只限制输出值变化频率,但 + 次 setMessages 照常触发。 | |
React.memo | 效果有限减少了历史 Part 重渲染,但当前消息的 reconciliation + Streamdown 解析仍每秒 + 次缓存 source-url filter 无感相对于主瓶颈占比极小。 | |
关键痛点的观点是。模型侧高频推送 chunk,导致 UI 层短时间内需要响应大量状态更新,线程被阻塞。
`useChat` 返回的 `messages` 是一个 React state。每当 stream 中有新 chunk 到达时:
│ ▼ 解析 SSE 事件 ← 从 HTTP stream 中解析出 UIMessageChunk │ ▼ 创建新 messages 对象 ← 不可变更新:新数组 + 新消息对象 + 新 parts 对象 │ ▼ 调用 setMessages ← 触发 React 状态更新 │ ▼ Chat 组件重渲染 ← 执行函数体、创建 React 元素、reconciliation │ ▼
React 层的 ``useDeferredValue``。``useThrottle`` 和 ``memo`` 都是作用在步骤上,无法从源头降低推送频率。
即使把节流调到 0.1 s,`useChat`仍然在每秒处理+个 chunk。每次都触发 `setMessages` → React 调度更新 → 组件函数执行。这些开销无法通过 React API 消除,因为 `chunk 到达的频率无法从组件层控制`。
useChat └── transport.sendMessages ← 返回 ReadableStream└── HttpChatTransport ← 发起 HTTP fetch。解析 SSE └── processResponseStream ← Uint8Array → UIMessageChunk
如果能在这个 stream 上加一个“节流阀”,就能从源头控制 `useChat` 收到数据的频率。
import { DefaultChatTransport。type ChatTransport,type HttpChatTransportInitOptions,type UIMessage,type UIMessageChunk } from 'ai';export class ThrottledChatTransport extends DefaultChatTransport {
private readonly idleTimeoutMs!: number,constructor {
const { idleTimeoutMs = 100,...transportOptions } = options?,{};super,this.idleTimeoutMs = idleTimeoutMs;}
// 重写 sendMessages 与 reconnectToStream,让返回 stream 经由 throttleStream 包装
override async sendMessages(options:
Parameters>) : Promise {
const stream = await super.sendMessages;return this.throttleStream;}
override async reconnectToStream(options:
Parameters>) :
Promise<(ReadableStream<UIMessageChunk>> | null>
{
const stream = await super.reconnectToStream;不过,if return null;怎么说呢,return this.throttleStream;}
private throttleStream :
ReadableStream
{
const reader = stream.getReader;const queue : UIMessageChunk =;const MAX_QUEUE_SIZE = Math.floor / );// 可调
const state : any = {
flushScheduled : false,sourceDone : false,cancelled : false,readIndex : 0。idleHandle : undefined as number | undefined | null,controller : null as ReadableStreamDefaultController | null,};const cancelIdle = => {
if return;const ric =
=>void })
.cancelIdleCallback?,=>clearTimeout);ric,state.idleHandle = null;},const flush = => {
state.flushScheduled = false;state.idleHandle = null;if return,const controller = state.controller!,try {
// 每次仅推一条
if {
controller.enqueue;
说起来,state.readIndex++;}
// 清空已消费完毕的数据块
if {
queue.length = 0;state.readIndex=0;}
// 若还有剩余则继续排下一次 flush,否则检查是否结束
if scheduleFlush;else if controller.close;} catch {}
};const scheduleFlush = => {
if return;state.flushScheduled=true;const ric =
(globalThis as { requestIdleCallback?:(
从cb来看,=>void),opts?:{timeout:number}
)=>number|undefined }).requestIdleCallback?,=>void),opts:{timeout:number})=>setTimeout);state.idleHandle=ric;},// new readable stream that consumes source asynchronously
return new ReadableStream({
async start{
state.controller=controller;try{
for{
// 背压:队列满时暂停读取等待消费追赶
while{
ifreturn;ifscheduleFlush;await new Promise);}
const {done,value}=await reader.read;if{
state.sourceDone=true;if
controller.close;else scheduleFlush;break,}
queue.push;scheduleFlush;}
}catch{
if
try{controller.error}catch{}
}finally{
reader.releaseLock;}
},cancel{
state.cancelled=true;cancelIdle,queue.length=0;void reader.cancel;}
}),按理说,}
}
// Chat.tsx
import { ThrottledChatTransport } from '@/lib/throttled-transport';const = useState =>
new ThrottledChatTransport({
idleTimeoutMs : 100,// 浏览器忙碌时最多等100ms再强制推一条
body : =>
})
);const { messages,...rest }= useChat;...
// 渲染部分省略…,
模型推送 chunk │▼HTTP fetch → ReadableStream │▼DefaultChatTransport.processResponseStream ← 解析为 UIMessageChunk │▼queue.push ← 入队 │├───────── 队列 ≥ MAXQUEUESIZE ────────┐ ││ 否 │ ││ 是 │ ││ ↓ ↓ ││ 暂停 reader.read │└─────────────────────────────────────┘ │▼requestIdleCallback │ 浏览器空闲时触发 → 出队→ useChat→ 渲染 └───────────────────────────────────────┘
使用 requestIdleCallback 能让 CPU 空闲才工作;只取一条保证增量最小,背压防止内存膨胀。不过,
📈 性能指标对比
| 指标 | 调整前 | 调整后 | Δ% |
|---|
Actually table should have correct rows:
I will rewrite performance comparison table correctly below
Performance Comparison
| 指标<\/th>\ | 调整前<\/th>\ | 调整后<\/th>\ | Δ<\/th>\ <\/tr> <\/ad> |
|---|---|---|---|
| wrapper push frequency <\/td>\ | 150+/s<\/td>\ | 15+/s <\/td>\ | -88%<\/tr> |
| \u300a重绘次数\u300a <\/ tdl?\ --- sorry let's just provide a simpler table without overcomplicated percentages. --- |
We'll skip complicated table and just give bullet list of metrics improvement. ---
We'll incorporate bullet list instead of complex table. --- We'll rewrite final part accordingly. --- Ok let's finish final answer properly without messing up formatting. --- "}
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback