Preact应用中通过Ajax下载文件触发浏览器安全错误
Preact + Vite Ajax 文件下载 SecurityError 问题分析与解决
问题原因
你遇到的安全错误,根源是npm init preact创建的项目默认集成了Preact Router(依赖preact-iso),它会拦截页面中所有<a>标签的点击事件,尝试通过history.pushState实现前端路由跳转。而你创建的下载链接使用blob:协议的URL,Router的拦截逻辑会把它当作要跳转的路由路径,触发pushState的同源安全校验,最终抛出错误。
旧项目没有路由拦截,浏览器能直接处理<a>标签的download属性,所以正常工作。
错误点说明
你的代码本身逻辑没问题,但忽略了Preact Router对原生<a>标签的事件劫持,导致原生下载行为被路由逻辑干扰。
正确实现方式
方法1:阻止事件冒泡,跳过Router拦截
在动态创建的下载链接上添加点击事件,阻止事件冒泡到Router的监听逻辑,让浏览器执行原生下载:
let response = await fetch("http://localhost:8080/api/entries/template"); if (response.ok) { let content = await response.text(); let type = "text/plain"; let downloadLink = document.createElement('a'); downloadLink.href = window.URL.createObjectURL(new Blob([content], { type })); downloadLink.download = 'filename.txt'; // 阻止事件冒泡,避免Preact Router劫持点击事件 downloadLink.addEventListener('click', (e) => { e.stopPropagation(); }); document.body.appendChild(downloadLink); downloadLink.click(); // 清理Blob URL和DOM元素,避免内存泄漏 setTimeout(() => { window.URL.revokeObjectURL(downloadLink.href); document.body.removeChild(downloadLink); }, 100); }
方法2:直接使用window.open下载(无需创建a标签)
绕开<a>标签,直接通过window.open打开Blob URL,触发下载:
let response = await fetch("http://localhost:8080/api/entries/template"); if (response.ok) { let content = await response.text(); let type = "text/plain"; const blobUrl = window.URL.createObjectURL(new Blob([content], { type })); // 注意:必须在用户交互上下文内执行(比如按钮点击回调中),否则会被浏览器弹窗拦截 const downloadWindow = window.open(blobUrl); // 清理Blob URL setTimeout(() => { window.URL.revokeObjectURL(blobUrl); // 可选:自动关闭下载窗口 if (downloadWindow) downloadWindow.close(); }, 100); }
方法3:使用Preact组件渲染隐藏下载链接
如果是在Preact组件中实现,可以直接渲染一个隐藏的<a>标签,并阻止路由拦截:
import { useRef } from 'preact/hooks'; function DownloadButton() { const linkRef = useRef(null); const handleDownload = async () => { let response = await fetch("http://localhost:8080/api/entries/template"); if (response.ok) { let content = await response.text(); let type = "text/plain"; linkRef.current.href = window.URL.createObjectURL(new Blob([content], { type })); linkRef.current.click(); setTimeout(() => { window.URL.revokeObjectURL(linkRef.current.href); }, 100); } }; return ( <> <button onClick={handleDownload}>下载模板</button> <a ref={linkRef} download="filename.txt" style={{display: 'none'}} onClick={(e) => e.stopPropagation()} /> </> ); }
兼容性说明
以上方法完全兼容Preact Router,核心思路都是避免路由逻辑干扰原生下载行为,同时保持Ajax请求自定义头的能力(满足你的API限制需求)。
内容的提问来源于stack exchange,提问作者derpirscher
相关产品推荐
相关产品推荐

