Getting "Unsafe attempt to load URL data:image/svg+xml..." in Safari using a through React component
I'm using rollup to bundle a react npm package that contains an icon component that takes a name as a prop and returns an Icon with that name wrapped by a react component. This is the component code:
import sprite from './public/sprite.svg';
function Icon({ name }) {
return <svg className="svg-wrapper">
<use href={`${sprite}#${name}`} />
</svg>
);
}
The folder structure is the following:
- src
- - public
- - - sprite.svg
- - icons
- - - some-icon.svg
- - - some-other-icon.svg
- - index.tsx # component with the code mentioned above
And this is my rollup config:
export default {
plugins: [
esbuild({
sourceMap: false,
target: "esnext"
}),
image(),
svgicons({
inputFolder: "src/icons",
output: "public/sprite.svg"
}),
json()
]
}
This works fine in Chrome (although it does inline all the svg inside of the href
which I think it's the purpose of this approach) but in Safari it triggers the following error:
Unsafe attempt to load URL data:image/svg+xml,%3c%3fxm ....
Domains, protocols and ports must match.
The thing is, as mentioned, this is an npm package that packages the icons as part of the js bundle (inlining) so there's not much control over how this component is served since this is handled by the browser caching (also one of the key points of using this approach). I'm quite familiar with CORS and I know that perhaps avoiding to use data:image/svg+xml
uri links would fix this but would increase the complexity of the build steps of this package (needing to build the icons using svgr/svgo and then have some kind of lookup table to give back the right icon based on the name prop i.e.).
So, ultimately my question is, with the sprite approach in a react component library is there a foolproof way of avoiding these kind of issues and cross-browser inconsistencies? Thanks in advance for any help provided.
Solution 1:
I have been struggling with this issue for a while. I guess this is a bug on Safari throwing an error because is dealing with a dataURI as if it was an external URL.
About your code, you could expose your sprite in a public folder or publish it in a cdn (good for caching purposes) and change the way rollup is handling your svg (it seems it is packing your svg as a dataURI). Alternatively, I implemented a workaround to convert the dataURI in a blob.
import sprite from './public/sprite.svg';
function dataURItoBlobUrl(dataURI: string) {
const svg = decodeURI(dataURI).split(',')[1];
const blob = new Blob([svg], { type: "image/svg+xml" });
return URL.createObjectURL(blob);
}
const blobUrl = dataURItoBlobUrl(sprite);
export const Icon: FC<IconProps> = ({ name, ...props }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" {...props}>
<use href={`${blobUrl}#${name}`}></use>
</svg>
);
};