sizes: an unprefixed fluid value below 50vw emits an invalid 0w srcset descriptor
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.5k
- Forks
- 331
- Avg merge
- 19h 52m
- Merged PRs (30d)
- 9
Description
tl,rd: This report is generated by Claude interacting with me while trying to solve an issue. I asked claude Opus 5 to write down an issue report. I apologize for the Claudish. This is producing a warning in the console and the way to fix it was in a NuxtImg component go from: sizes="10vw s:10vw m:10vw l:5vw" to sizes="xxs:10vw s:10vw m:10vw l:5vw", prepending a breakdown descriptor.
Environment
@nuxt/image 2.1.0 (latest). The same code is on main today.
Reproduction
<NuxtImg src="/droplet.webp" :width="206" :height="206" sizes="22vw s:22vw m:22vw l:7vw" />
Renders:
srcset="/_ipx/s_70x70/droplet.webp 0w, /_ipx/s_132x132/droplet.webp 132w, …"
Chrome:
Failed parsing 'srcset' attribute value since its 'w' descriptor is invalid.
Dropped srcset candidate "/_ipx/s_70x70/droplet.webp"
Cause
The docs say an unprefixed entry is allowed: "If you omit a screen size prefix (like sm:) then this size is the 'default' size of the image."
parseSizes files that entry under a synthetic key named '1px' (src/runtime/utils/index.ts:96). That key exists only to sort the default below every real breakpoint. It is not a viewport width.
But getSizesVariant then reuses it as one (src/runtime/image.ts:211):
const screenMaxWidth = (ctx.options.screens && ctx.options.screens[key]) || Number.parseInt(key)
Number.parseInt('1px') === 1, and the fluid value is converted against it (:224):
_cWidth = Math.round((_cWidth / 100) * screenMaxWidth) // → Math.round(n / 100)
So the candidate width is Math.round(n / 100): 0 for any n <= 49, 1 for n >= 50.
The zero-guard at :220 runs before the conversion, so it cannot catch the result it produces:
let _cWidth = Number.parseInt(size)
if (!screenMaxWidth || !_cWidth) return undefined // 1 and 22 → passes
if (isFluid) _cWidth = Math.round(...) // → 0, unguarded
The 50vw threshold
Measured against default screens:
sizes |
descriptors |
|---|---|
100vw sm:50vw md:400px (the docs' own example) |
1w 2w 320w 400w 640w 800w, valid |
50vw sm:50vw md:400px |
1w 2w 320w 400w 640w 800w, valid |
49vw sm:50vw md:400px |
0w 320w 400w 640w 800w, invalid |
22vw sm:22vw md:400px |
0w 141w 282w 400w 800w, invalid |
The documented example is safe only because 100vw rounds up to 1. Note that it still emits 1w and 2w candidates, a one-pixel and a two-pixel image, so the sentinel produces junk even when it does not produce invalid output.
Suggested fix
Minimally, move the zero-check after the conversion so a degenerate default is dropped rather than emitted:
if (isFluid) {
_cWidth = Math.round((_cWidth / 100) * screenMaxWidth)
}
if (!screenMaxWidth || !_cWidth) {
return undefined
}
More correct would be to stop treating the default bucket as a 1px screen at all. It has no viewport width, so the fluid value arguably should resolve against the smallest configured screen instead. That would also remove the junk 1w/2w candidates from the documented example.
Workaround
Prefix the first entry with the smallest configured screen alias (xxs:22vw). This emits a byte-identical sizes attribute, because finaliseSizeVariants overwrites each entry's media query with the next entry's, so the first key's own breakpoint is only ever used for the _cWidth computation.
Possibly related
#1133, closed as not_planned without diagnosis.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the NuxtImg example, then inspect parseSizes in src/runtime/utils/index.ts and getSizesVariant in src/runtime/image.ts, especially the synthetic 1px key and fluid-width conversion. Compare the minimal post-conversion guard with resolving the default bucket against a configured screen. Done means the unprefixed values no longer emit a 0w descriptor and the resulting srcset is valid.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nuxt, typescript
- Domain
- frontend, performance, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 74/100