AI 爆款文章创作器前端最值得优化的六个方向,按收益排序是:SSE 流式渲染合并、长列表虚拟滚动、Markdown 解析缓存、路由与组件代码分割、请求防抖节流、静态资源与图片懒加载。这个项目里流式渲染和 Markdown 解析属于”运行时性能”,直接影响打字机效果的流畅度;代码分割和资源优化属于”加载性能”,决定首屏白屏时长。下面每个方向都给出可运行的做法和项目里实际量到的改进。
一、性能问题先量化再动手
优化前先用两个工具定位瓶颈:rollup-plugin-visualizer 看打包体积构成,Chrome DevTools 的 Performance 面板记录流式生成时的长任务。项目早期发现首屏 bundle 超过 2MB,主因是 highlight.js 全量引入和 markdown-it 插件打包进主 chunk;运行时则确认 markdown-it 每次渲染全量解析是主线程长任务的源头。没有这些数据,优化方案会落到猜测上。
二、六个改进方向
2.1 SSE 流式渲染合并
| 方向 | 改动前 | 改动后 |
|---|---|---|
| 流式写入 | 每 chunk 触发渲染,FPS 掉到 20 以下 | rAF 合并,稳定 60 FPS |
| Markdown 渲染 | 全文每次重渲染,3000 字耗时 80ms | 段落级缓存,增量解析 <10ms |
| 长列表 | 万级文章列表 DOM 上万节点,滚动卡顿 | 虚拟列表,DOM 稳定在 30 个左右 |
流式输出阶段,每收到一个 SSE chunk 就更新响应式状态,会触发整页依赖重渲染。前端在 store 里维护累积 buffer,用 requestAnimationFrame 批量提交,一帧内到达的所有 chunk 只触发一次视图更新。代码写法在状态管理方案里已给出,核心是”高频增量 → 低频批量”。
2.2 长列表虚拟滚动
文章管理页的历史记录是典型的万级长列表,一次性渲染所有 DOM 节点会让滚动掉帧。方案是只渲染可视区加缓冲区的条目。项目用 vue-virtual-scroller 的 RecycleScroller 处理不定高条目(文章卡片高度随标题行数变化),配置 min-item-size 做初始布局估算。实测从原生渲染 10000 条改为虚拟列表后,首屏渲染从秒级降到百毫秒级,滚动 FPS 从 18 左右回到 60。
<RecycleScroller
class="article-list"
:items="articles"
:item-size="72"
key-field="id"
>
<template #default="{ item }">
<ArticleCard :article="item" />
</template>
</RecycleScroller>
配合虚拟列表的两个细节:v-for 的 key 用业务 id 而非索引,避免列表插入删除时整列重建;列表项里不做深层 reactive 绑定,改为 shallowRef 持有数组、整体替换触发更新。
2.3 Markdown 解析缓存
AI 生成的 Markdown 每次全文重渲染是最大的运行时浪费。优化分成两级:同一篇已渲染段落缓存 HTML,只对新追加的尾部做增量解析;文章从历史记录重新打开时直接读缓存,不走解析。加上 md.utils.escapeHtml 兜底代码块,解析链路本身在 3000 字文章上稳定在 10ms 内。
2.4 路由与组件代码分割
首屏只该加载登录页和骨架,创作工作台、编辑器、历史列表都按路由懒加载:
- 路由组件全部改
() => import('@/views/xxx.vue'),Vite 自动分包; - 主入口按需引入 highlight.js 的 core 版 + 手动
registerLanguage注册常用语言,语言包从数百 KB 降到几十 KB; - 引入
rollup-plugin-visualizer校验每个 chunk 构成,超过 200KB 的 chunk 拆分或按需加载。
// router 懒加载
const routes = [
{
path: '/generation',
component: () => import('@/views/generation/index.vue'),
},
{
path: '/history',
component: () => import('@/views/history/index.vue'),
},
]
改完首屏 bundle 从 2MB 降到约 300KB(gzip),登录页白屏时间明显缩短。生成工作台这类高频页可以加预加载——用户 hover 菜单项时触发 router.resolve(path).matched[0].components.default(),提前拉取 chunk。
2.5 请求防抖与节流
创作器有三个高频操作容易打爆接口:标题联想搜索、阶段切换时的自动保存、列表滚动触底的翻页加载。前两个用防抖(300ms 内只保留最近一次输入触发请求),翻页用节流或 IntersectionObserver 触底加载。防抖还能顺带减少服务端压力,一个搜索框在输入 20 个字符时只发 1 次请求而不是 20 次。
import { useDebounceFn } from '@vueuse/core'
const search = useDebounceFn(async (kw) => {
const res = await fetch(`/api/titles?kw=${encodeURIComponent(kw)}`)
suggestions.value = await res.json()
}, 300)
2.6 图片与静态资源优化
文章封面和模板缩略图是资源体积大头。三个动作按收益排序:WebP 替换(Vite 构建时用 vite-plugin-imagemin 转格式,体积平均降 60%);loading="lazy" + decoding="async" 让可视区外图片不阻塞;静态资源部署到 CDN 并配置 Cache-Control,历史封面第二次打开直接走强缓存。
三、优化后的验证
六个方向全部落地后,用 Lighthouse 跑一次前后对比:首屏 LCP 从 3.8s 降到 1.6s,Performance 面板里流式生成期间不再出现超过 50ms 的长任务,虚拟列表滚动稳定 60 FPS。验证要固定浏览器环境和网络条件再对比,否则数据没有说服力。
3.1 优先级排布
六个方向不该平均用力,项目里的实际排布分三批:第一批是流式渲染合并和 Markdown 解析缓存,解决打字机卡顿这类”用户立刻感知”的问题,改动量小收益最大;第二批是代码分割和虚拟列表,解决首屏时长和长列表滚动,属于加载与交互体验的底子;第三批是防抖节流和图片优化,属于锦上添花,可以在前两批稳定后逐步补齐。反过来如果先做图片优化,流式生成照样卡,用户体感不会有变化。
3.2 不建议做的优化
有两个方向在创作器里被验证为负收益:一是给所有组件套 v-memo,列表项本来就低频变更,缓存命中率低反而增加 diff 判断成本;二是过度拆分 chunk,把每个工具函数单独打包,HTTP 请求数暴增,弱网下加载更慢。优化的前提是量化证据,没有长任务和体积数据支撑的方案先不落地。
常见问题(FAQ)
Q1:性能优化先做哪一项?
先用 visualizer 和 Performance 量化,流式渲染合并和代码分割收益最大、成本最低。
Q2:虚拟列表和分页怎么选?
万级以上且滚动浏览用虚拟列表,千级以下直接分页,避免引入库的维护成本。
Q3:Markdown 渲染卡顿怎么定位?
Performance 面板录制生成过程,长任务若在 markdown 解析函数里,就上段落级缓存。