ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

前端 Word 在线预览:docx-preview、PDF.js 与服务端方案

2026/9/30 8:28:42 拓冰建站 浏览量
前端 Word 在线预览:docx-preview、PDF.js 与服务端方案 前端如何实现 Word 在线预览这个问题我前后在三个项目里踩过不同的坑。第一次是后台管理系统要预览合同第二次是教育平台要展示试卷第三次是企业知识库要解析文档。表面上都是“打开一个 .docx 文件”但浏览器原生根本不认识 Word 的 OOXML 格式它只认 HTML、PDF、图片和少数媒体格式。所谓在线预览本质是把 Word 文件转换成浏览器能渲染的东西再套一层交互壳。有人上来就写 iframe结果 Chrome 直接下载文件有人用纯前端库发现公式全丢也有人上了重型文档服务部署完发现维护成本比业务还高。这篇内容我会把前端实现 Word 在线预览的几条主流路线拆开讲包括纯前端解析、服务端转 PDF、组件封装、微前端接入、大文件处理和常见问题排查。适合正在做后台管理、在线教育、知识库、合同系统的前端开发也适合准备前端面试时想搞清楚“浏览器为什么不能直接预览 Word”的同学。1. 先想清楚Word 在线预览到底难在哪1.1 浏览器不认 .docx不是加个 iframe 就完事.docx 文件本身是一个 ZIP 压缩包里面装着 XML、图片、字体、关系文件和样式定义。把后缀改成 .zip 解压后你能看到word/document.xml、word/styles.xml、word/media/、word/_rels/这些目录。浏览器不认识这些 XML 的业务含义它只负责按 HTML 和 CSS 渲染。你直接给 iframe 一个 .docx 地址浏览器发现没有内置查看器大多数情况下会触发下载而不是显示内容。这个行为不是前端代码写得不对而是浏览器能力边界决定的。所以前端要做预览必须选一条“转换路线”要么在浏览器里用 JavaScript 解析 OOXML 并生成 DOM要么把文件传到后端用办公套件转成 PDF 再交给 PDF.js要么接入支持文档渲染的在线服务。三条路线没有绝对优劣只有场景匹配度。我在实际项目里最怕的是需求方说“就要和 Word 里一模一样”因为这句话背后往往包含分页、页眉页脚、公式、图表、字体、批注、修订记录等一堆细节。先承认差异再谈方案项目会顺利很多。1.2 预览目标不同方案完全不同同样是 Word 在线预览业务目标可能差得很远。第一种是内容型预览比如知识库、论坛附件、简历解析用户只要看到文字和图片排版稍微乱一点可以接受。这种场景用 mammoth.js 把 docx 转成干净 HTML 最舒服体积小、速度快、方便做搜索高亮。第二种是保真型预览比如合同、标书、试卷、公文表格列宽、分页、页眉页脚、公式都必须尽量还原这时候 docx-preview 或服务端转 PDF 更合适。第三种是协作型预览用户不仅要看还要在线编辑、多人同时改、留痕、批注那就不是纯前端能扛的了需要 OnlyOffice、Collabora 这类文档服务或者接入成熟的云文档能力。我在选型时一般先问三个问题文件最大多少里面有没有公式和复杂表格用户是否需要复制文字和搜索这三个答案基本能砍掉一半方案。比如 50MB 以上的 Word 文件纯前端解析很容易把主线程卡死必须走服务端转换加缓存。再比如试卷里全是数学公式mammoth.js 直接转 HTML 会丢公式得单独处理 OMML 或走 PDF。1.3 一张选型表先定方向把常见方案放在一起对比选型会快很多。下面这张表是我根据实际项目整理的保真度、速度、实现成本都是相对值不是绝对标准。方案核心原理保真度包体积/部署公式支持复杂表格适合场景docx-preview前端解析 OOXML生成 DOM中高前端包约几百 KB部分支持依赖实验特性较好后台合同、试卷、普通文档mammoth.js前端解析并转语义化 HTML中低前端包较小基本丢失一般知识库、简历、内容提取LibreOffice PDF.js服务端转 PDF前端渲染高需后端服务高高公文、标书、高保真预览OnlyOffice/Collabora文档服务渲染很高部署重高高在线协作、编辑第三方云文档云服务转换高依赖外部高高快速上线、可接受外部依赖这张表不是让你死记而是帮你快速排除。比如项目只有两三个人的小团队没有运维资源却要做高保真合同预览那优先考虑前端 docx-preview 加服务端 LibreOffice 兜底。如果公司已经有文档服务那就别重复造轮子。如果只是内部知识库用户对格式不敏感mammoth.js 最省事。选型时还要考虑一个隐藏成本字体。Word 里常用的宋体、黑体、仿宋、Calibri浏览器环境不一定有。前端渲染只能靠系统字体或 web font 映射服务端转 PDF 则要在服务器安装对应字体否则 PDF 里也会乱码或替换字体。这个坑我在一次公文项目里遇到过本地开发正常服务器上仿宋变成默认无衬线后来在 Docker 镜像里装了字体才解决。2. 纯前端方案docx-preview 与 mammoth.js 怎么选2.1 docx-preview 的渲染逻辑与适用边界docx-preview 的思路是直接在浏览器里解析 .docx 的 ZIP 包读取document.xml、styles.xml、numbering.xml、settings.xml等文件然后把段落、表格、图片、页眉页脚映射成 DOM 节点。它比 mammoth 更接近 Word 的排版模型支持分页符、页面背景、部分页眉页脚、列表编号和表格边框。实际用起来renderAsync接收 ArrayBuffer、目标容器和样式选项返回 Promise。它不会把文档转成 HTML 字符串而是直接往容器里塞 DOM所以你可以用 CSS 继续控制预览区域。它的边界也很明显复杂公式、SmartArt、图表、EMF/WMF 图片、文本框、艺术字、修订记录支持有限。尤其是公式docx-preview 对 OMML 的支持属于“能显示一部分但别指望完全还原”。如果你的文档里有大量 MathType 或 Word 自带公式最好先拿真实文件做验证。另一个注意点是分页breakPages: true会按节属性插入分页但页眉页脚和页码不一定和 Word 完全一致。我一般会告诉产品这是“高度接近”不是“像素级还原”。如果需求方坚持像素级那就走服务端转 PDF。2.2 mammoth.js 为什么适合内容型场景mammoth.js 的目标不是还原排版而是把 Word 的语义结构转成干净 HTML。它会把标题映射成h1到h6把加粗映射成strong把斜体映射成em把列表映射成ul/ol把表格映射成table。它默认会忽略大部分字体、字号、颜色、页边距和分页信息所以输出结果很清爽特别适合知识库、文章导入、简历解析、搜索引擎索引。它的 API 也很简单mammoth.convertToHtml({ arrayBuffer })返回value和messagesmessages里会告诉你哪些样式被忽略了。图片可以通过convertImage选项转成 base64但大图会让 HTML 体积暴涨我一般会改成上传到对象存储再替换 URL。mammoth 最大的问题是公式和复杂表格。公式基本会丢表格列宽会被压扁合并单元格有时也会出问题。如果你只是要文字内容这些都能忍如果你要展示试卷那就不合适。我通常把 mammoth 当成“内容提取器”而不是“预览器”。需要搜索高亮时它生成的 HTML 结构清晰做关键词匹配很方便需要保真时就换 docx-preview 或 PDF。2.3 Vue3 最小可用示例docx-preview 渲染本地文件先装依赖。docx-preview 本身依赖 JSZip 解析压缩包实际安装时它会带相关依赖但显式装一下更稳。npm i docx-preview jszip下面是一个 Vue3 组合式 API 的预览组件支持父组件传入 File 对象。核心步骤是读取 ArrayBuffer然后调用renderAsync。template div classpreview-wrap div v-ifloading classpreview-loading文档加载中.../div div v-iferror classpreview-error{{ error }}/div div refcontainerRef classdocx-container/div /div /template script setup import { ref, onMounted, watch, nextTick } from vue import { renderAsync } from docx-preview const props defineProps({ file: { type: File, required: true, }, }) const containerRef ref(null) const loading ref(false) const error ref() async function renderDocx(file) { if (!file || !containerRef.value) return loading.value true error.value try { const arrayBuffer await file.arrayBuffer() await nextTick() containerRef.value.innerHTML await renderAsync(arrayBuffer, containerRef.value, null, { className: docx-preview, inWrapper: true, ignoreWidth: false, ignoreHeight: false, breakPages: true, experimental: true, renderHeaders: true, renderFooters: true, renderFootnotes: true, }) } catch (e) { console.error(e) error.value 文档解析失败请确认文件是否为有效的 .docx 格式 } finally { loading.value false } } onMounted(() { renderDocx(props.file) }) watch(() props.file, (newFile) { renderDocx(newFile) }) /script style scoped .preview-wrap { width: 100%; height: 100%; overflow: auto; background: #f5f6f8; } .docx-container { padding: 24px; display: flex; justify-content: center; } .docx-container :deep(.docx-preview) { background: #fff; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); padding: 40px 56px; min-width: 794px; box-sizing: border-box; } /style这段代码能跑通大部分普通文档。几个细节值得注意inWrapper会生成一个外层容器方便你控制阴影和背景breakPages打开后会按分页符显示多页experimental打开后对某些新特性支持更好但也可能带来样式差异。容器清空要放在渲染前避免切换文件时旧 DOM 残留。如果文件很大file.arrayBuffer()本身也会占用内存所以我在真实项目里会加文件大小限制比如超过 20MB 就提示走服务端预览。另外不要直接把用户上传的 Word 文件 URL 交给 docx-preview 去 fetch跨域和鉴权容易出问题先通过接口拿到 ArrayBuffer 再渲染更可控。2.4 踩坑样式、字体、公式、图片docx-preview 虽然能还原不少样式但下面几个坑我几乎每次都遇到。第一是字体映射。Word 里写的是“宋体”浏览器可能渲染成默认衬线解决办法是在预览容器上设置 font-family或者用 web font 引入常用字体。第二是图片格式。Word 里常见的 EMF、WMF 是矢量图浏览器不支持docx-preview 也处理不了表现是图片位置空白。这种情况只能在服务端转成 PNG 或者提前用工具转换。第三是公式。OMML 公式在 docx-preview 里可能显示为空白或乱码如果文档里公式很多我会建议走服务端转 PDF或者单独把公式节点转成 MathML 再用 MathJax 渲染。第四是表格列宽。docx-preview 会根据w:tblGrid和单元格宽度生成 colgroup但浏览器表格布局算法和 Word 不完全一样表现是某些列被压窄。可以在预览容器里给 table 加table-layout: fixed再根据 colgroup 设置宽度但不要全局覆盖否则会破坏简单表格。第五是页眉页脚。docx-preview 的页眉页脚支持属于“能渲染”但页码、奇偶页不同、首页不同这些高级设置不一定准。我的经验是先拿真实文件做一轮验证把不支持的特性列出来给产品确认比上线后被投诉强。3. 工程化方案LibreOffice PDF.js 为什么更稳3.1 服务端转换的收益与代价如果你追求高保真服务端用 LibreOffice 把 Word 转成 PDF前端用 PDF.js 渲染是目前最稳的开源组合。LibreOffice 的转换引擎对 .doc、.docx、.rtf、.odt 支持都很好能处理公式、表格、页眉页脚、分页、字体嵌入输出 PDF 后浏览器渲染 PDF 的能力又非常成熟。前端不需要解析 OOXML不需要担心样式差异也不用把大文件读进内存。代价是你需要维护一个转换服务要考虑并发、超时、字体安装、临时文件清理、安全隔离和缓存。很多小团队一开始不想上服务端结果纯前端方案在复杂文档上反复翻车最后还是补了服务端。我的建议是如果文档主要是内容型纯前端足够如果文档涉及合同、标书、试卷、公文直接上服务端转 PDF别在纯前端上耗太多时间。服务端转换还有一个好处是统一了预览格式前端只需要一个 PDF 查看器移动端、PC 端、小程序 webview 都能用。3.2 LibreOffice Headless 转换实操在服务器上安装 LibreOffice 后可以用 headless 模式执行转换。最基础的命令是这样soffice --headless --convert-to pdf --outdir /data/preview /data/upload/contract.docx这条命令会把contract.docx转成contract.pdf放到/data/preview。实际生产环境不能这么裸跑要加几个关键参数。第一是用户配置隔离避免多个转换进程争抢同一个用户目录导致锁死soffice --headless \ -env:UserInstallationfile:///tmp/lo_profile_${REQUEST_ID} \ --convert-to pdf \ --outdir /data/preview \ /data/upload/contract.docx第二是超时控制LibreOffice 偶尔会卡住外层要有 timeout比如 30 秒没完成就杀掉进程并返回失败。第三是并发限制不要一次性起几十个 soffice内存会爆我一般用队列控制并发数比如同时只跑 2 到 4 个具体看服务器配置。第四是字体安装服务器上要装宋体、黑体、仿宋、Times New Roman 等常用字体否则 PDF 里会替换字体。第五是宏安全Word 文件可能带宏LibreOffice 默认不执行宏但生产环境仍要把转换服务放在隔离容器里限制文件系统权限。转换完成后把 PDF 上传到对象存储或静态目录前端拿到 URL 再渲染。不要每次预览都实时转换同一个文件哈希对应同一份 PDF缓存起来能省大量 CPU。3.3 前端 PDF.js 预览与交互PDF.js 是 Mozilla 开源的 PDF 渲染库前端用起来很成熟。先安装npm i pdfjs-dist在 Vite 项目里worker 文件要单独引入否则会报错。下面是一个基础的多页渲染示例import * as pdfjsLib from pdfjs-dist import workerSrc from pdfjs-dist/build/pdf.worker.min.mjs?url pdfjsLib.GlobalWorkerOptions.workerSrc workerSrc export async function renderPdf(url, container) { const loadingTask pdfjsLib.getDocument({ url, cMapUrl: /cmaps/, cMapPacked: true, }) const pdf await loadingTask.promise container.innerHTML for (let pageNo 1; pageNo pdf.numPages; pageNo) { const page await pdf.getPage(pageNo) const viewport page.getViewport({ scale: 1.5 }) const canvas document.createElement(canvas) const context canvas.getContext(2d) canvas.width viewport.width canvas.height viewport.height canvas.style.display block canvas.style.margin 0 auto 16px container.appendChild(canvas) await page.render({ canvasContext: context, viewport, }).promise } return pdf }这个版本适合页数不多的文档。页数多的时候要加懒加载只渲染可视区域附近的页面否则一次性创建几百个 canvas 会把内存打满。PDF.js 还支持文本层可以在 canvas 上方叠加一层透明文本实现文字选择和搜索。如果你需要关键词高亮可以监听文本层找到匹配的 span 再加背景色。缩放控制也简单重新计算 viewport 的 scale 再渲染即可。移动端要注意触摸手势可以配合pdfjs-dist的 viewer 或者自己监听 pinch 事件。我一般会把 PDF.js 封装成一个查看器组件对外暴露上一页、下一页、缩放、搜索、下载这些方法业务层不用关心底层 API。3.4 安全与性能别把转换服务裸奔服务端转换服务一旦暴露攻击面比纯前端大很多。第一限制上传文件类型和大小只允许 .doc、.docx、.rtf、.odt最大不要超过 50MB超大文件直接拒绝。第二转换进程要跑在低权限用户下容器里不要挂载敏感目录临时文件定期清理。第三防止命令注入不要把用户文件名直接拼到 shell 命令里用参数数组或临时重命名。第四加频率限制同一个用户一分钟最多转换几次避免被刷。第五转换失败要返回明确错误码不要把服务器路径暴露给前端。性能方面转换是 CPU 密集型任务最好独立部署不要和业务 API 混在同一台机器上。可以用 Redis 队列做异步任务前端先拿到任务 ID轮询状态转换完成后再拿 PDF URL。这样用户体验也更好不会因为转换慢导致请求超时。缓存策略按文件 MD5 或 SHA1 做 key同一文件只转一次不同用户上传相同文件也能复用。3.5 缓存与异步任务设计一个完整的预览流程可以设计成前端上传文件后端计算文件哈希查 Redis 是否已有 PDF。如果有直接返回 PDF URL如果没有把文件写入临时目录投递转换任务返回任务 ID。前端轮询任务状态成功后用 PDF.js 渲染。这个设计把同步等待变成了异步任务适合大文件和高并发。哈希计算可以在前端用 Worker 做上传时带上哈希后端直接复用避免重复计算。临时文件要设置 TTL比如 24 小时自动删除。PDF 文件可以放对象存储设置私有读前端通过签名 URL 访问。转换服务要记录日志包括文件哈希、耗时、成功失败原因方便排查。还有一个细节如果用户上传的是 .doc 旧格式LibreOffice 也能转但最好在前端提示“旧版 Word 格式建议另存为 .docx 后预览”因为旧格式的兼容问题更多。异步任务的状态可以设计为 pending、processing、success、failed前端根据状态显示 loading、进度条或错误提示。这样即使转换排队用户也知道系统在工作。4. 大项目集成组件封装、微前端与大文件4.1 封装成可复用预览组件无论选哪种方案都建议把预览能力封装成独立组件而不是散落在业务页面里。组件对外暴露统一的 props文件对象或 URL、预览模式、是否显示工具栏、水印文案、主题。内部根据文件类型和大小自动选择渲染器小文件优先 docx-preview大文件或公式多的走服务端 PDF。组件还要处理 loading、error、empty 三种状态并提供onLoaded、onError、onPageChange事件。如果业务需要搜索高亮可以再加一个searchTextprop。封装时注意不要把具体渲染库的 API 暴露出去否则以后换方案会改很多业务代码。我一般会写一个DocPreview组件内部维护策略对象比如{ docx: renderDocx, pdf: renderPdf, image: renderImage }根据文件扩展名和配置选择。这样业务层只需要DocPreview :filefile /。组件卸载时一定要清理资源PDF 要调用pdf.destroy()docx-preview 要清空容器事件监听要移除否则在单页应用里反复切换页面很容易内存泄漏。4.2 在 qiankun 微前端中接入的注意点如果项目用了 qiankun 或类似的微前端框架文档预览组件可能被多个子应用复用。这里有几个坑要注意。第一是样式隔离docx-preview 生成的 DOM 带有全局类名PDF.js 的 canvas 也可能被外部 CSS 影响最好把预览容器放在 Shadow DOM 里或者用 scoped 样式加高优先级选择器。第二是生命周期子应用卸载时要主动销毁预览实例停止轮询任务取消未完成的请求否则主应用切换路由后会报内存泄漏警告。第三是公共依赖如果多个子应用都装 docx-preview 和 pdfjs-dist包体积会重复可以通过 qiankun 的 shared 依赖或 externals 把公共库抽到主应用。第四是路由和基座通信预览组件需要的文件接口、鉴权 token、用户信息最好通过 props 或主应用提供的全局状态注入不要让子应用各自请求。第五是主题微前端下主应用切换暗色模式预览组件要能响应 CSS 变量。实际做的时候我会把预览器做成一个独立的 npm 包或 monorepo 子包子应用按需引入版本统一管理避免每个子应用各改一份。4.3 大文件上传与 Worker 预处理大 Word 文件直接读进主线程解析很容易造成页面卡顿。一个常见优化是分片上传加 Worker 计算哈希。前端把文件切成 2MB 到 5MB 的分片每个分片单独上传同时用 Worker 计算整个文件的 SHA1 或 MD5。Worker 里可以用crypto.subtle.digest或者增量哈希库避免主线程被占满。上传完成后后端合并分片并返回文件 ID预览时直接用文件 ID 请求转换结果。如果非要在前端解析大文件也可以把解析工作放进 Worker但 docx-preview 依赖 DOM不能直接在 Worker 里跑所以只能在 Worker 里做 ZIP 解压和 XML 预处理再把结果传回主线程组装。更现实的做法是超过 10MB 或 20MB 的 Word 文件前端不做解析直接走服务端转换。这样既省内存又避免不同浏览器兼容问题。Worker 还可以用来做文件类型嗅探通过读取文件头判断是不是真正的 docx防止用户改后缀。这个细节在安全要求高的系统里很有用。4.4 国际化与主题适配文档预览组件经常被多语言系统复用文案不能写死。加载中、解析失败、下载、上一页、下一页、缩放、搜索、水印这些文案都要走 i18n。水印内容也可能需要根据语言和用户信息动态生成。主题方面预览区域通常需要跟随系统或应用主题。浅色模式下 docx-preview 的页面背景是白色深色模式下如果直接反色文档里的图片和颜色会变得很奇怪。我的做法是文档页面始终保持白底黑字周围工具栏和背景跟随主题这样既保证可读性又不破坏文档原始观感。PDF.js 渲染的 canvas 本身也是白底不需要额外处理。如果产品强制要求暗色文档可以用 CSS filter 反转再反转图片但效果一般不推荐。另外预览容器要支持自定义宽高和滚动移动端要适配触摸滚动和双指缩放。国际化还有一个隐藏点日期、页码格式、文件大小单位这些也要本地化。5. 常见问题与排查技巧实录5.1 公式、图片、表格列宽高频问题公式是 Word 在线预览里最容易翻车的部分。Word 自带公式是 OMML 格式mammoth.js 基本不支持docx-preview 支持一部分但复杂公式会显示异常。如果文档公式多首选服务端转 PDF因为 LibreOffice 对 OMML 和 MathType 公式支持较好。如果必须纯前端可以尝试把 OMML 转成 MathML再用 MathJax 或 KaTeX 渲染。具体做法是解析document.xml找到m:oMath节点用 XSLT 或第三方库转成 MathML然后替换成占位元素最后在预览完成后调用 MathJax 排版。图片方面EMF、WMF 浏览器不支持docx-preview 会跳过或显示空白服务端转 PDF 能正确渲染。表格列宽问题核心是w:tblGrid和单元格w:tcW。docx-preview 会生成 colgroup但如果你用全局 CSS 覆盖 table 宽度可能破坏原有比例。我的建议是不要给预览区 table 设置width: 100%而是保留 docx-preview 生成的宽度必要时加table-layout: fixed。如果产品要求表格列宽可拖动那说明它已经不是单纯预览了需要进入编辑态纯前端方案很难低成本实现。5.2 内存泄漏与卡顿排查预览组件最常见的性能问题是内存泄漏。PDF.js 的PDFDocumentProxy不销毁反复打开文件会一直占内存表现是页面越用越卡最后崩掉。每次关闭预览时都要调用pdf.destroy()并清空 canvas。docx-preview 渲染出来的 DOM 节点很多切换文件前要innerHTML 同时移除事件监听。如果用了URL.createObjectURL生成临时链接记得URL.revokeObjectURL。轮询转换任务时组件卸载要清除定时器。图片懒加载可以用 IntersectionObserver但卸载时要observer.disconnect()。卡顿方面大文件不要一次性渲染所有页面PDF.js 可以只渲染可视区域docx-preview 可以按页拆分渲染。如果页面里有大量公式MathJax 排版也会卡可以延迟到用户滚动到对应位置再排版。还有一个容易忽略的点控制台里不要长期保留大对象引用比如把整个 ArrayBuffer 挂到 window 上调试生产环境一定要删掉。5.3 面试高频考点速查表前端面试里问 Word 在线预览通常不是考你会不会用某个库而是考你对浏览器能力、文件格式、性能优化和工程方案的理解。下面这些点经常被问到。面试问题回答要点浏览器为什么不能直接打开 docxdocx 是 ZIP 包包含 OOXML浏览器没有内置解析器直接访问会触发下载纯前端预览 Word 有哪些方案docx-preview 保真渲染mammoth.js 转语义 HTMLPDF.js 配合服务端转 PDF公式丢失怎么办OMML 转 MathML或服务端 LibreOffice 转 PDF前端用 MathJax 兜底大文件如何优化分片上传、Worker 算哈希、服务端转换、PDF 分页懒加载、缓存如何防止 XSSmammoth 输出 HTML 要 DOMPurify 清洗服务端转换隔离文件名不要拼命令微前端下如何接入封装独立组件Shadow DOM 样式隔离卸载时销毁实例公共依赖抽离如何做缓存按文件哈希缓存 PDFRedis 记录任务状态对象存储存结果如何做搜索高亮PDF.js 文本层匹配或 mammoth 生成的 HTML 做关键词标记这些问题的答案不需要背但你要能结合项目讲出取舍。面试官更想听到“我为什么选这个方案”“遇到了什么坑”“怎么解决”而不是“我用了某个库”。5.4 我的避坑清单最后整理一份我实际项目里踩过的坑按优先级排列供你参考。先确认文档里有没有公式、EMF 图片、复杂表格有的话优先服务端转 PDF。不要相信“纯前端一定能高保真”docx-preview 和 Word 的渲染差异要提前告诉产品。服务器一定要装常用中文字体否则 PDF 字体替换会很难看。LibreOffice 转换要加超时、并发限制和用户目录隔离否则进程会卡死。同一个文件按哈希缓存 PDF不要每次预览都重新转换。前端解析大文件前先看大小超过 20MB 直接走服务端别硬扛。PDF.js 用完要 destroydocx-preview 切换文件要清空容器避免内存泄漏。mammoth 输出的 HTML 一定要用 DOMPurify 清洗防止 XSS。微前端里预览组件要封装成独立包卸载时清理定时器、请求和观察器。国际化和主题适配要提前做不要等业务铺开后再补改动成本很高。我现在接新需求时会先拿一份最复杂的真实文件做技术验证而不是拿一份简单文档跑通就开工。验证内容包括公式能不能显示、表格列宽是否错乱、页眉页脚是否正常、大文件加载是否卡顿、移动端是否可滚动缩放。这一轮验证通常只需要半天但能省下上线后大量返工。纯前端方案适合快速上线和内容型场景服务端转 PDF 适合高保真和复杂文档两者不是互斥的很多项目最终都是“小文件前端解析大文件服务端转换”的混合策略。真正重要的不是选哪个库而是搞清楚你的文档长什么样、用户最在意什么、团队能维护什么。把这三个问题想明白前端实现 Word 在线预览就不会变成无底洞。