nuxt / nuxt/image

sizes: an unprefixed fluid value below 50vw emits an invalid 0w srcset descriptor

Open
#2,320 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.