
Claude 這次在網頁端和桌面端做了一件看起來不大、實際很關鍵的事把長回復的流式渲染速度提升了大約 4 倍。這里的“提速”不在模型側而在前端渲染側。模型生成的 token 數量沒有變變快的是頁面把 token 流變成可讀文本、Markdown、代碼塊、可滾動長文這一整條處理鏈路。長回復是 LLM 產品里最容易暴露體驗短板的地方。寫長文、生成完整代碼、做長文檔對話時如果渲染線程跟不上 token 到達速度就會出現“字已經輸出完了但頁面還要卡幾秒才能滾動”“輸入框打字掉幀”“代碼塊高亮出得很慢”這類現象。Claude 這次優化的目標就是把這些卡頓壓下去。這篇文章會做三件事先拆解長回復流式渲染的性能瓶頸到底在哪再給一條可落地的前端流式渲染優化路徑包含增量解析、任務切片、Web Worker 處理和 fetch 流式讀取示例最后給出一套不依賴 Claude 內部實現的性能驗證方法你可以直接用在手頭的 AI 產品上。適合閱讀的讀者正在寫 AI Chat 產品的前端工程師、負責 LLM 應用性能優化的開發者以及想搞清楚“流式渲染提速”到底在優化什么的技術負責人。1. 核心能力速覽先說清楚一個前提Claude 網頁端與桌面端長回復流式渲染提速 4 倍這個口徑來自外部公開信息。官方沒有放出足夠詳細的基準測試原始數據所以本文不會聲稱“復現了 4 倍提升”而是把這個事件當作引子重點講流式渲染優化的通用工程方法。具體能提升多少必須在自己的項目里做前后對比。能力項說明優化產品Claude 網頁端、桌面客戶端優化場景長回復、長文本輸出的流式渲染優化方向前端渲染鏈路不是模型推理速度提升幅度官方口徑約 4 倍具體以實際測試為準常見技術方向token 增量處理、Markdown 流式解析、渲染任務調度可復用范圍所有帶 LLM 流式輸出的 Web 應用、桌面端應用驗證方式模擬流式接口 Chrome DevTools Performance主要收益減少滾動卡頓、降低主線程占用、提升打字響應結論放在前面這類優化的核心不是“把模型輸出變快”而是“讓頁面在輸出過程中不卡”。下面按瓶頸、優化路徑、驗證方法展開。2. 長回復流式渲染為什么會卡頓2.1 渲染速度跟不上 token 到達速度大模型流式輸出的速度并不慢。一個正常的大模型接口平均每秒能返回幾十到上百個 token。放到界面上這就是每秒幾十次網絡回調。如果前端每收到一個 chunk 就立刻做一次完整 DOM 更新渲染任務就會以每秒鐘二三十次的頻率壓到主線程上。卡頓的根本原因是節奏不匹配網絡層是高頻小包渲染層是低吞吐大任務。每次更新如果還要觸發 Markdown 重解析、代碼高亮、布局計算主線程很快就會被占滿。2.2 全量重解析是最大消耗很多 AI 聊天頁面最初實現時是把已收到的全部文本當作一個整體字符串每次有新的 token 到達就用這個字符串重新走一遍 Markdown 解析和 HTML 渲染。文本短的時候問題不大一旦累計到幾千字、幾萬字每次重解析的成本會跟著全文長度一起上漲。這種實現的復雜度是 O(n)n 是當前已生成的文本長度。token 越多每次更新越慢到長回復后期單次解析可能就要幾百毫秒。用戶感知就是生成到一半頁面越來越卡。2.3 高頻 DOM 更新占滿主線程LLM 聊天界面里常見的 DOM 更新動作包括替換正文容器、更新代碼高亮、調整滾動位置、更新 token 計數。這些動作如果高頻觸發會在 Performance 面板里形成一連串 Long Task。瀏覽器主線程被這些任務占據后輸入框的 keydown 事件排隊等待用戶打字就會出現明顯延遲。長回復場景里還有一個隱藏問題全文一直掛在 DOM 上沒有分頁或虛擬滾動。當頁面節點數量從幾百漲到幾千、幾萬瀏覽器在樣式重算和重繪上的時間會顯著增加。2.4 長文本滾動區域破壞滾動手勢用戶看長回復時通常希望邊生成邊往上讀同時又能隨時拖動滾動條。如果每次新 token 到達都強制滾動到底部會打斷用戶的閱讀節奏如果完全不滾動用戶又看不到最新內容。這種“滾動策略”處理不好體驗上比渲染性能問題更明顯。更隱蔽的是布局抖動。每次新內容插入到滾動容器頂部或底部瀏覽器都要重新計算滾動高度一旦插入位置和滾動位置互相影響就可能出現滾動條跳變。3. 提速 4 倍的常見技術路徑下面這些優化路徑是流式渲染場景下通用的工程做法。Claude 的具體實現細節沒有公開但同類產品要在這里提效通常會沿著這幾個方向做。3.1 全量重渲染改為增量渲染核心改動是維護一個“已經渲染到哪了”的游標。每次收到新 chunk只處理新增部分而不是把整個歷史文本重新解析一遍。已渲染的歷史內容保持不動新內容追加到容器尾部。這樣單次更新時間不隨全文長度增長整體復雜度從 O(n) 降為 O(增量)。3.2 把穩定內容和實時內容分開管理長回復里Markdown 段落一旦閉合就不會再變化。代碼塊、列表、標題都屬于穩定結構。可以先把這些穩定內容渲染成正式 DOM 節點中間的臨時 token 流放到一個輕量的“游標區間”里。等到游標區間積累到成段內容再提升為正式節點。這樣避免了每來一個 token 都操作整棵 DOM 樹。3.3 代碼塊和列表專項渲染代碼塊是長回復里最重的渲染對象。一次生成幾百行代碼時語法高亮可能比 Token 流本身更耗時。常見做法是代碼渲染先降級為普通文本展示高亮任務通過異步分片或 Worker 完成。列表和表格同理不急著實時渲染復雜樣式先保證文本可見。3.4 請求調度而不是每包必渲染網絡回調和渲染任務不必一一對應。可以先把文本寫入緩沖區再通過 requestAnimationFrame 或 requestIdleCallback 統一提交。比如每 50 到 100 毫秒刷新一次界面把期間累積的增量一次渲染。犧牲一點“逐字輸出”的觀感換來主線程空閑和滾動流暢。3.5 Web 端與桌面端共用渲染核心桌面端通常基于 WebView 或 Electron渲染核心如果能和 Web 端復用同一套邏輯維護成本會低很多。優化一次兩端同時生效。這也是 Claude 網頁和桌面端能同步提速的原因之一。4. 環境準備與流式渲染驗證方案要驗證流式渲染性能不一定要接真實大模型。準備一個能模擬長回復輸出的流式接口再配合瀏覽器性能工具就能完成大部分實驗。4.1 準備一個可控的流式輸出源推薦使用 FastAPI 或 Node.js 啟動一個本地服務用 SSE 格式按固定間隔推送文本。這樣能控制 token 的到達速度方便前后對比。# requirements: fastapi uvicorn from fastapi import FastAPI from fastapi.responses import StreamingResponse import asyncio app FastAPI() async def generate(): chunk 這是用于測試流式渲染性能的長文本內容。 for i in range(200): yield fdata: {chunk} 第{i 1}段\n\n await asyncio.sleep(0.05) app.post(/api/stream) async def stream(): return StreamingResponse(generate(), media_typetext/event-stream)啟動命令uvicorn main:app --host 127.0.0.1 --port 80004.2 確定核心指標驗證流式渲染性能建議先固定幾項指標指標名稱定義觀察方式TTFT從發起請求到第一段內容可見DevTools Network字符渲染速率每秒實際出現在屏幕上的字符數視頻錄制或腳本統計長任務耗時主線程上超過 50ms 的任務Performance 面板交互延遲輸入框敲字到字符出現的時間Input 事件分析滾動幀率滾動長文本時的 FPSPerformance 面板 FPS 圖4.3 用 DevTools Performance 記錄現場打開 Chrome DevTools切到 Performance 面板點擊錄制然后在頁面上發起一次長回復生成。等輸出結束后停止錄制重點看 Long Task、Recalculate Style 和 Layout 三條記錄。如果 Long Task 出現在每次網絡回調之后說明當前渲染實現確實在和 token 流搶主線程。這個現場記錄就是優化的基線數據。5. 流式渲染優化落地示例下面給三組代碼。第一組是常見但性能較差的基線條案第二組改成游標增量更新第三組把 Markdown 解析放到 Worker 中。它們不是完整生產實現但能直接跑通并體現優化思路。5.1 基線版本每個 chunk 都全量更新// 基線版本每收到一個 chunk 就全量替換 innerHTML const container document.getElementById(output); const response await fetch(/api/stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ prompt: 寫一篇長文 }) }); const reader response.body.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const text decoder.decode(value, { stream: true }); container.innerHTML text; // 每次全量重解析長文后越來越卡 }這個版本的問題很直觀每次網絡包到達都會走一次全量解析和 DOM 替換。文本從 1000 字漲到 10000 字時單次處理時間會顯著上升。5.2 改進版本游標增量提交const container document.getElementById(output); let fullText ; let renderedLength 0; function flushIncremental() { const newText fullText.slice(renderedLength); if (!newText) return; // 先把純文本放進去避免每次解析 Markdown const span document.createElement(span); span.textContent newText; container.appendChild(span); renderedLength fullText.length; } const response await fetch(/api/stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ prompt: 寫一篇長文 }) }); const reader response.body.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; fullText decoder.decode(value, { stream: true }); // 使用 requestIdleCallback 降低渲染優先級 if (typeof requestIdleCallback function) { requestIdleCallback(flushIncremental, { timeout: 200 }); } else { setTimeout(flushIncremental, 50); } }這個版本把“全量替換”改成了“只追加新增部分”同時通過 requestIdleCallback 合并渲染任務避免每個網絡包都強制觸發一次布局。5.3 進一步改進在 Web Worker 里解析 Markdown主線程只負責 DOM 提交把 Markdown 解析這種計算型工作交給 Worker。// markdown.worker.js // 這里用簡化邏輯演示實際項目可換成 markdown-it 或 marked self.onmessage (event) { const { fullText, renderedLength } event.data; const newText fullText.slice(renderedLength); // 模擬 Markdown 解析只把換行轉成 p 塊 const html newText .split(\n\n) .map((block) p${block}/p) .join(); self.postMessage({ html, length: newText.length }); };// 主線程中創建 Worker 并接收解析結果 const container document.getElementById(output); const worker new Worker(./markdown.worker.js); worker.onmessage (event) { const { html } event.data; const temp document.createElement(div); temp.innerHTML html; container.appendChild(...temp.children); }; worker.postMessage({ fullText, renderedLength });注意Worker 版本適合把“解析字符串”從主線程移走但 DOM 替換本身仍要發生在主線程。如果想徹底避免主線程頻繁操作 DOM還需要對長文本做分頁或虛擬滾動這部分屬于下游工程。6. 長回復場景下的 API 與流式鏈路設計6.1 流式響應的數據格式主流 LLM 接口都支持 SSE 或 fetch stream。服務端按事件流推送增量內容前端通過 ReadableStream 讀取。data: {id:1,delta:你} data: {id:1,delta:好} data: {id:1,delta:今天繼續寫這篇長文}前端讀取流時注意用 TextDecoder 的{ stream: true }參數處理多字節字符被分包的情況否則中文可能亂碼。6.2 超時、中斷與重連長回復接口不適合短超時。普通 REST 接口可能 10 到 30 秒超時但流式接口在生成一個幾千字回復時整體耗時可能達到幾十秒甚至幾分鐘。超時策略應該基于“兩個 chunk 之間的間隔”而不是“整個請求的總時長”。前端還要處理中斷場景用戶點擊停止生成、頁面切換、桌面端 WebView 被系統回收。建議做好“流中斷時保留已生成文本”的快照邏輯。6.3 長回復與批量任務流式渲染解決的是“用戶在頁面上看著內容生成”的場景。一次生成即一個流式響應。批量任務則不同把大量長文本生成任務放進隊列通過任務 ID 輪詢或回調獲取結果。兩種模式對應不同的接口設計。如果要做批量長文生成可以這樣設計任務狀態{ task_id: task_123456, status: running, total_chunks: 200, completed_chunks: 87, output: }批量任務不需要前端實時渲染每一段重點是任務進度的可追蹤性和失敗重試。7. 性能觀察與對比方法7.1 前后對比的實驗設計如果你想在項目里復現類似優化實驗流程分四步。第一步固定測試文本。為了避免隨機性建議準備一個 3000 到 5000 字的固定長文本按固定間隔推送。第二步固定測試環境。同一瀏覽器、同一設備、關閉不必要的插件必要時用瀏覽器無痕模式。第三步記錄基線。用未優化的版本跑一遍記錄 Long Task 數量、累計主線程阻塞時間和滾動幀率。第四步切換優化版本重復同樣流程對比數據。7.2 不要只盯著“倍數”官方說 4 倍提升這個數字是從特定測試環境和指標下得出的。你在自己的頁面里優化后可能只提升了 30%也可能提升了 10 倍。這都很正常。關鍵是看主線程占用是否降下來了、長任務是否變少、用戶能否在生成過程中流暢滾動手動閱讀。7.3 性能觀察的常見誤區第一個誤區是只看 TTFT。TTFT 只反映第一段內容的到達時間長回復的卡頓主要集中在后半程。第二個誤區是忽略滾動場景。很多測試只盯著自動輸出沒有在輸出過程中模擬手動滾動。第三個誤區是忘記檢查頁面內存。長期掛著一個超大 DOM 樹即使渲染不卡內存也會持續增長桌面端尤其明顯。8. 常見問題與排查方法問題現象可能原因排查方式解決方案長文本滾動卡頓高頻 DOM 更新占滿主線程Performance 面板查看 Long Task 頻率批量提交增量用 requestIdleCallback 調度代碼塊出現時頁面掉幀語法高亮在主線程同步執行觀察代碼塊首次渲染時的耗時高亮任務放到 Worker 或延遲到空閑期打字輸入延遲明顯渲染任務和 input 事件搶占主線程輸入框事件響應延遲分析降低渲染優先級預留輸入事件處理間隙桌面端 WebView 內存暴漲歷史節點持續增加沒有淘汰觀察 DOM 節點數和堆內存曲線引入虛擬滾動限制容器內節點數流式輸出中斷代理或網關斷開空閑連接查看網絡請求斷開時間點配置更長超時時間前端加自動重連中文輸出亂碼TextDecoder 未用流式模式解碼檢查解碼參數使用 new TextDecoder() 并傳 { stream: true }頁面強制滾到底部打斷閱讀滾動策略沒有區分用戶意圖觀察滾動事件發生時機只在用戶位于底部時自動滾動這些問題的共同點是不要讓一次性渲染任務長時間占據瀏覽器主線程。9. 最佳實踐與使用建議9.1 工程落地建議第一次接入流式渲染時先用模擬接口跑通鏈路不要直接接真實模型。把“網絡接收、文本緩存、渲染提交、滾動控制”四層拆開分別測試。這樣出了問題能快速定位。項目目錄建議分開管理模型輸出文本、渲染后 HTML、高亮后的代碼塊、滾動位置緩存。避免在狀態對象里存一整套扁平字符串然后每次全量重新生成。9.2 合規與安全邊界長回復內容可能包含代碼、文檔、個人信息。涉及業務數據時要確保流式接口有權限校驗避免中間人截獲敏感內容。接口服務最好限制訪問范圍不要暴露到公網。如果生成結果涉及人臉、聲音、他人作品或未公開文檔發布前必須確認授權。流式渲染只是技術展示不能繞過內容合規審核。10. 總結與下一步這次 Claude 網頁端與桌面端的長回復流式渲染提速本質是前端渲染鏈路的優化不是模型輸出變快了。它提醒所有做 LLM 產品的人模型把文字“說”出來只是完整鏈路的前半段后半段“讓用戶舒服地看到文字”是否高效直接決定產品體驗。最值得先做的驗證是在你自己的 Chat 頁面上用固定長文本做一次基線測試看 Performance 面板里是否出現大量長任務。如果長任務密集就按本文的增量渲染、批量提交、Worker 解析三個方向逐步改造。最容易踩的坑是“只優化了第一屏”。長回復的卡頓通常出現在文本累計到一定長度之后驗證時一定要跑到長文本后段手動滾動畫一畫輸入框敲幾個字整套體驗順了才算真正完成。后續可以繼續擴展的方向包括長文本虛擬滾動、代碼高亮按需加載、桌面端離線緩存、批量生成任務隊列。每一步都可以用同一套性能觀察流程驗證效果。建議收藏備用。