← dompdf.js Studio

dompdf.js 跨域图片与 CORS 处理完整指南

网页里的图片经常来自 CDN、图床或第三方服务,与页面不同源;把这些跨域图片放进 PDF,是 dompdf.js 开发者最常踩的坑之一。跨域本身不会让图片加载失败,但 PDF 渲染管线要读取图片的像素数据,而浏览器安全模型规定:未经 CORS 授权的跨域图片,脚本无法读取其内容,渲染结果要么缺失、要么被安全策略拦截。理解同源策略与 CORS 机制,是解决这类问题的前提。本文从同源与跨域的区别讲起,逐步介绍服务器响应头配置、fetch 拉取转 dataURL、代理转发与同源部署等四种主流方案,对比各自的适用场景与代价,最后给出跨域问题排查清单,帮助开发者彻底告别跨域图片导致的 PDF 空白与报错。

同源策略与图片读取权限

浏览器的同源策略规定,页面只能读取与自身同源(协议、域名、端口一致)的资源内容。普通 img 标签展示跨域图片不受限制,但 canvas 与 PDF 渲染这类需要读取像素数据的场景,会因跨域图片未授权而被污染或拦截,这正是跨域图片在 PDF 生成中失败的根源,理解了它就理解了问题全貌。

dompdf.js 的渲染管线运行在浏览器内,同样受这套安全模型约束:模板中的跨域图片可以正常显示在网页上,但生成 PDF 时可能取不到图像数据。要解决它,必须让图片服务器明确授权,通过 CORS 响应头声明允许当前页面读取,或者绕开跨域,把图片先变成同源数据。

判断问题是否由跨域引起,有一个简单方法:把图片地址换成同源资源或 data URL,PDF 立刻正常,基本可以确定是 CORS 问题。反之,若同源图片也失败,问题更可能出在路径、格式或加载时序上,先做这个对照实验,避免在错误的层面排查,能省下大量时间。

CORS 响应头与服务器配置

让跨域图片可读的标准做法是配置 CORS 响应头。核心是 Access-Control-Allow-Origin,它声明哪些源可以读取资源:值设为星号表示所有源可读,适合公开的 CDN 图床;生产环境更推荐显式列出允许的域名,配合 Vary: Origin 让缓存层正确区分不同来源的响应,避免缓存串源。

对于带凭证的请求(如需要 Cookie 的鉴权图床),Access-Control-Allow-Origin 不能使用星号,必须返回具体来源,并同时设置 Access-Control-Allow-Credentials 为 true。大多数图片 GET 请求属于简单请求,不需要预检;但带自定义头的请求会触发 OPTIONS 预检,服务器也要正确响应,否则请求会被拦截。

配置位置取决于图片服务形态:自建 Nginx 可在 location 块加 add_header Access-Control-Allow-Origin 对应值;对象存储(OSS、S3)通常在控制台开启跨域规则;第三方图床则要看服务商是否提供 CORS 配置能力,没有的话只能改用代理或下载方案,这一点在选型时就该确认清楚。

代码示例:fetch 拉取转 dataURL

当图片服务器无法配置 CORS 时,最通用的方案是改用 fetch 拉取图片再转成 data URL 嵌入模板。fetch 同样受同源策略约束,但它可以携带请求头并明确处理跨域,配合代理或服务端转发即可突破限制;转换后的 data URL 与页面同源,PDF 管线读取毫无障碍,方案通用性最强。

转换过程分三步:fetch 获取图片二进制、转成 Blob、再用 FileReader 读出 base64 数据。data URL 方案把图片直接写进模板字符串,自包含、可离线,缺点是体积比原图大约三分之一,批量大图时要注意模板体积与内存占用,需要权衡使用场景。

下面的代码把跨域图片转为 data URL 后插入模板:fetch 请求成功拿到 Blob,FileReader 异步读出 base64 字符串,拼接成 data:image/png;base64 前缀后放入 img 标签。所有步骤都是标准 Web API,不需要引入额外依赖,错误处理里对加载失败的图片给出占位提示,避免生成缺图文档。

需要提醒的是,fetch 转换方案要求目标服务器允许跨域读取,否则 fetch 本身也会失败;此时需要结合代理或服务端转发使用。把 toDataUrl 封装为公共工具后,页面与导出流程共用同一套转换与降级逻辑,遇到来源变更只需调整一处,维护成本低、行为一致。

import { DomPDF } from 'dompdf.js';

async function toDataUrl(url) {
  const res = await fetch(url);
  const blob = await res.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}

const imgUrl = 'https://cdn.example.com/photos/cover.jpg';
const dataUrl = await toDataUrl(imgUrl);

const html = `
  <style>body { font-family: 'Source Han Sans SC', sans-serif; }</style>
  <h2>封面图示例</h2>
  <img src="${dataUrl}" style='width:100%'>`;

const pdf = new DomPDF({ format: 'A4', margin: '20mm' });
pdf.addPage(html, { format: 'A4' });
pdf.save('cors-demo.pdf');

代理方案与同源部署对比

当图片服务完全不可控(无 CORS、无公开访问),可以自建同源代理:后端接收图片 URL,代为请求并把字节流原样返回,前端访问代理地址即同源请求,彻底绕开跨域。代理还能顺带做鉴权、缓存与限流,适合企业内部系统的图片统一出口,安全与性能一并解决。

同源部署是更彻底的做法:把图片从第三方迁移到自己的静态资源服务,与页面同源,天然没有跨域问题,还能统一走缓存与 CDN 策略。代价是迁移成本与存储成本,适合图片量可控、长期稳定的业务;临时接入的第三方图片,用代理或 data URL 更划算,不必大动干戈。

三种方案可以并存:核心资产图同源部署,第三方素材走代理,临时内容用 fetch 转 data URL。选择标准是图片的来源可控性与使用频率——高频使用的图片值得迁移,低频一次性内容直接内嵌即可。按这个思路组合,跨域问题基本不会成为瓶颈,方案也随业务演进自然升级。

缓存、凭证与常见坑

CORS 与缓存叠加时容易出隐蔽问题:图片被 CDN 或浏览器缓存后,如果缓存响应缺少 CORS 头,即使源站配置正确也会读取失败。解决办法是源站与缓存层都正确返回 Access-Control-Allow-Origin,并配置 Vary: Origin,让不同来源的请求拿到各自的缓存副本,避免一个缓存响应服务所有来源。

另一个常见坑是重定向:图片 URL 跳转后,浏览器对最终响应的跨域检查以重定向后的地址为准,若跳转目标未配置 CORS 头同样会失败。排查时先确认最终响应头,而不是只检查最初请求的地址;必要时把重定向后的地址直接作为图片源,从源头消除跳转变量。

此外,混合内容与凭证策略也值得留意:HTTPS 页面引用 HTTP 图片会被浏览器拦截,需要统一升级为 HTTPS;带 Cookie 的图片请求要按凭证模式处理,fetch 时指定 credentials。把这几条与响应头检查一起纳入排查清单,跨域问题就能快速定位、一次解决,不再反复试错。

跨域问题排查清单

遇到跨域图片生成失败,按以下顺序排查:先用浏览器开发者工具查看图片请求的响应头,确认 Access-Control-Allow-Origin 是否存在且允许当前源;再看请求是否经过重定向,最终响应是否携带 CORS 头;然后确认图片实际加载成功,状态码为 200 而不是 403 或 404,逐层排除服务端因素。

确认服务端配置无误后,检查前端环节:模板图片是否用了正确地址、fetch 是否指定了必要的模式与凭证、转换后的 data URL 是否完整可用。前端与后端各排查一遍,通常十分钟内就能定位根因,把问题收敛在单一环节,避免在多个可能性之间反复横跳浪费时间。

最后建立预防机制:图片接入统一走工具函数,同源优先、代理兜底、data URL 应急;每次新增图片来源时先验证 CORS 再上线。把方案沉淀成团队规范后,跨域图片基本不会再成为事故源,PDF 生成链路保持在可控、可预期的状态,团队协作也更顺畅。

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。