Merlin FuchsSoftware Engineer · Germany
Notes

Observable Framework silently double-encodes a data URI favicon

I had this in observablehq.config.js and no page on my data site showed an icon:

head: `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,${encodeURIComponent(favicon)}">`,

The markup it produced had xmlns%3D where it should have had xmlns=, which is not valid SVG, so the browser just gave up.

Framework normalises every href it renders with encodeURI(decodeURI(href)). The catch is that decodeURI deliberately refuses to decode escapes for reserved characters, = and / and # among them. So %3D survives the decode as the literal four characters %3D, and then encodeURI escapes its % into %25:

encodeURI(decodeURI("xmlns%3D%22a%22"))
// "xmlns%253D%2522a%2522"

Characters like < and " and space round-trip fine, which is what makes it confusing to look at. Only the reserved ones come out mangled.

Base64 avoids it entirely, because its alphabet is A-Za-z0-9+/= and both functions pass all of those through untouched:

head: `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${Buffer.from(favicon).toString("base64")}">`,