Skip to content

CSS minifier strips whitespace inside quoted url() strings, breaking inline SVG data URIs #19

@sn

Description

@sn

Bug

nitro build's CSS minifier strips whitespace inside quoted url() string values, including raw SVG data URIs. This silently breaks any inline SVG used in CSS (e.g. mask: url("data:image/svg+xml;utf8,<svg ...>...</svg>")), turning every icon based on this technique invisible in production. nitro dev does not minify, so the bug doesn't show up locally — only after deploy.

Reproduction

src/styles/main.css:

.icon {
  width: 16px;
  height: 16px;
  display: inline-block;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/></svg>") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/></svg>") center / contain no-repeat;
}

Then:

nitro build --no-fingerprint

Inspect the emitted CSS in build/assets/styles/main.css:

Expected (whitespace inside the quoted URL preserved):

.icon{...mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/></svg>") center / contain no-repeat}

Actual (whitespace inside the quoted URL collapsed):

.icon{...mask:url("data:image/svg+xml;utf8,<svgxmlns='http://www.w3.org/2000/svg'viewBox='002424'fill='none'stroke='currentColor'stroke-width='2'stroke-linecap='round'stroke-linejoin='round'><circlecx='12'cy='12'r='10'/></svg>") center / contain no-repeat}

Note: <svg xmlns=<svgxmlns= (broken element), viewBox='0 0 24 24'viewBox='002424' (broken attribute), <circle cx=<circlecx= (broken element). The browser cannot parse this SVG, so the mask is empty and the icon doesn't render. No console error.

Real impact

I hit this on a production deploy (fridaynight.wiki). Eight different icons across the site were invisible after deploy, including a "Buy me a coffee" button, the search-input magnifier, type-chip icons on every card, and the prev/next pager arrows. All were perfectly visible in nitro dev.

The failure mode is silent: no warning, no error, no broken layout — just missing icons that pass every test except a visual one.

Workaround

URL-encode the SVG payload so the troublesome characters (spaces, <, >) become %20/%3C/%3E. The minifier won't touch them after that:

mask: url("data:image/svg+xml;utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'...%3E");

Or use base64:

mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0...");

Both work, both are uglier than raw inline SVG.

Suggested fix

The CSS minifier must not touch string contents. The CSS spec is clear: the value inside the quotes of url("...") is a string token — it is opaque, and minifiers should preserve it byte-for-byte. The same goes for any other quoted string in CSS (content:, font-family, --var: "...", etc.).

A simple guard: when tokenizing CSS, recognize url( followed by a quote, scan to the matching close-quote, and emit those bytes verbatim — never collapse whitespace inside them.

If switching minifiers, libraries that get this right include lightningcss, cssnano (with default config), and csso. Whatever's currently in nitro build is collapsing inside strings, which is incorrect.

Environment

  • nitro-cli (current as of 2026-04-19)
  • macOS, Python 3.12
  • Triggered by both local nitro build --no-fingerprint and the same command run in GitHub Actions deploying to Cloudflare Pages.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions