Next.js@15.2.4中pdfjs-dist@4.10.38渲染PDF文本异常排查
问题:Next.js中pdfjs-dist渲染PDF文本损坏,纯React环境正常
在Next.js@15.2.4应用中使用pdfjs-dist@4.10.38渲染PDF文件时,出现文本损坏的情况,但相同代码和依赖版本在纯React应用中可正常展示PDF。
简化代码结构:
PDFViewer.tsx(主组件)
import { GlobalWorkerOptions, getDocument } from "pdfjs-dist"; import { useEffect, useState } from "react"; import PDFPage from "./PDFPage"; GlobalWorkerOptions.workerSrc = "/pdf.worker.min.js"; export default function PDFViewer() { const [pdfDoc, setPdfDoc] = useState(null); useEffect(() => { getDocument("/pdf/example.pdf").promise.then(setPdfDoc); }, []); if (!pdfDoc) return null; return ( <> {Array.from({ length: pdfDoc.numPages }, (_, i) => ( <PDFPage key={i} pdf={pdfDoc} pageNumber={i + 1} scale={1.5} /> ))} </> ); }
PDFPage.tsx(子组件)
import { useEffect, useRef, useState } from "react"; export default function PDFPage({ pdf, pageNumber, scale }) { const canvasRef = useRef(null); const [viewport, setViewport] = useState(null); useEffect(() => { let cancelled = false; pdf.getPage(pageNumber).then((page) => { const vp = page.getViewport({ scale }); setViewport(vp); const canvas = canvasRef.current; const context = canvas.getContext("2d"); const dpr = window.devicePixelRatio || 1; canvas.width = vp.width * dpr; canvas.height = vp.height * dpr; canvas.style.width = `${vp.width}px`; canvas.style.height = `${vp.height}px`; context.scale(dpr, dpr); const renderTask = page.render({ canvasContext: context, viewport: vp }); renderTask.promise.catch((err) => { if (!cancelled) console.error("Render error:", err); }); }); return () => { cancelled = true; }; }, [pdf, pageNumber, scale]); return ( <div> <canvas ref={canvasRef} /> </div> ); }
原因分析
核心问题是Next.js的服务端渲染(SSR)/静态生成(SSG)特性导致的:
- pdfjs-dist的Worker初始化、PDF渲染依赖浏览器环境的DOM API(比如
canvas.getContext)和window对象,而Next.js在服务端渲染阶段会执行组件代码,此时这些浏览器API不存在,会导致渲染逻辑异常。 - Worker文件的路径在服务端和客户端的解析规则不同,可能导致Worker加载失败,进而影响文本渲染。
修复方案
1. 让PDF组件仅在客户端渲染
使用Next.js的dynamic导入,禁用SSR,确保组件只在浏览器端执行:
在使用PDFViewer的地方修改导入方式:
import dynamic from 'next/dynamic'; // 禁用SSR,组件仅在客户端加载 const PDFViewer = dynamic(() => import('./PDFViewer'), { ssr: false });
2. 修正Worker初始化逻辑
将Worker的初始化移到useEffect中(仅在客户端执行),同时可以改用CDN路径避免本地路径问题:
修改PDFViewer.tsx:
import { GlobalWorkerOptions, getDocument } from "pdfjs-dist"; import { useEffect, useState } from "react"; import PDFPage from "./PDFPage"; export default function PDFViewer() { const [pdfDoc, setPdfDoc] = useState(null); useEffect(() => { // 仅在客户端初始化Worker GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.10.38/pdf.worker.min.js'; // 或者用本地public目录的文件:'/pdf.worker.min.js',确保文件已放到public目录 getDocument("/pdf/example.pdf").promise.then(setPdfDoc); }, []); if (!pdfDoc) return null; return ( <> {Array.from({ length: pdfDoc.numPages }, (_, i) => ( <PDFPage key={i} pdf={pdfDoc} pageNumber={i + 1} scale={1.5} /> ))} </> ); }
3. 增强PDFPage组件的客户端兼容性
在PDFPage组件中,确保canvas存在后再执行渲染逻辑,避免服务端渲染时的空引用错误:
修改PDFPage.tsx的useEffect部分:
useEffect(() => { let cancelled = false; const canvas = canvasRef.current; if (!canvas) return; // 确保canvas存在 pdf.getPage(pageNumber).then((page) => { if (cancelled) return; const vp = page.getViewport({ scale }); setViewport(vp); const context = canvas.getContext("2d"); if (!context) return; // 确保获取到canvas上下文 const dpr = window.devicePixelRatio || 1; canvas.width = vp.width * dpr; canvas.height = vp.height * dpr; canvas.style.width = `${vp.width}px`; canvas.style.height = `${vp.height}px`; context.scale(dpr, dpr); const renderTask = page.render({ canvasContext: context, viewport: vp }); renderTask.promise.catch((err) => { if (!cancelled) console.error("Render error:", err); }); }); return () => { cancelled = true; }; }, [pdf, pageNumber, scale]);
4. 验证资源路径
- 确保
pdf.worker.min.js已放到Next.js的public目录下(如果用本地文件),路径对应/pdf.worker.min.js。 - 确保PDF文件
/pdf/example.pdf存在于public/pdf/目录下。
内容的提问来源于stack exchange,提问作者Itay Tur
相关产品推荐
相关产品推荐

