Skip to content

fix: handle falsy src in NuxtImg and NuxtPicture#2218

Open
Bombastickj wants to merge 5 commits into
nuxt:mainfrom
Bombastickj:main
Open

fix: handle falsy src in NuxtImg and NuxtPicture#2218
Bombastickj wants to merge 5 commits into
nuxt:mainfrom
Bombastickj:main

Conversation

@Bombastickj
Copy link
Copy Markdown

@Bombastickj Bombastickj commented Apr 23, 2026

📚 Description

When src: undefined combined with custom: true & sizes, the preload link in <head> and the image load in onMounted can incorrectly resolve to current-url/undefined.

This causes NuxtImg to compute invalid values for the sizes computed, for example:

{
  "sizes": "32px",
  "src": undefined,
  "srcset": "undefined 32w, undefined 64w"
}

Which are then used within the component regardless of incorrect values.

Final edit:

  • Prevent undefined from being coerced into strings within srcset and sizes
  • Fix getSizes in image.ts to return empty values for falsy inputs
  • Remove unsafe non-null assertions in NuxtImg and NuxtPicture
  • Add parity test cases for both components to verify src: undefined handling

When these three props are combined, the preload link in `<head>` and the image load in `onMounted` can incorrectly resolve to `current-url/undefined`.

This causes `NuxtImg` to compute invalid values, for example:

{
  "sizes": "32px",
  "src": undefined,
  "srcset": "undefined 32w, undefined 64w"
}
@Bombastickj Bombastickj requested a review from danielroe as a code owner April 23, 2026 21:25
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 23, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/image@2218

commit: 06c05d4

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The changes short-circuit sizing logic when image input is falsy, avoid non-null assertions by casting props.src where needed, prevent placeholder resolution when props.src is missing, guard server-side preload on props.src, and delay assigning img.sizes/srcset until a mainSrc exists. Two unit tests were added to verify components render safely when src is undefined.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: handle falsy src in NuxtImg and NuxtPicture' accurately summarizes the primary change across all modified files—preventing undefined from being coerced into strings and removing unsafe non-null assertions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly explains the bug (src: undefined causing invalid srcset/sizes), the root causes, and the specific fixes applied across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/runtime/components/NuxtImg.vue (2)

106-129: ⚠️ Potential issue | 🟡 Minor

Same src guard likely needed for prerender block.

The new && props.src check correctly avoids emitting a preload link with an undefined href. However, the prerender block immediately below (lines 127-129) still calls prerenderStaticImages(src.value, sizes.value.srcset, …) without an analogous guard. With props.src undefined, src.value and sizes.value.srcset can resolve to ".../undefined" and "undefined 32w, …" respectively, so the prerenderer may attempt to fetch and persist garbage URLs at build time.

🛡️ Suggested guard
 // Prerender static images
-if (import.meta.server && import.meta.prerender) {
+if (import.meta.server && import.meta.prerender && props.src) {
   prerenderStaticImages(src.value, sizes.value.srcset, useRequestEvent())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/NuxtImg.vue` around lines 106 - 129, The prerender
block calls prerenderStaticImages(src.value, sizes.value.srcset,
useRequestEvent()) even when props.src is undefined, which can produce garbage
URLs; wrap that call with the same server+src guard used above — e.g., ensure
the condition includes import.meta.server, import.meta.prerender and props.src
(or truthiness of props.src) before invoking prerenderStaticImages so src.value
and sizes.value.srcset are not used when props.src is missing.

98-147: ⚠️ Potential issue | 🟠 Major

Guard at the source: mainSrc can still resolve to ".../undefined" when props.src is unset.

The if (props.sizes) wrap prevents bogus sizes/srcset assignments, but the underlying img.src = mainSrc.value on line 141 can still be set to a bad URL. When props.src is undefined but props.sizes is set, mainSrc falls through to sizes.value.src, which is computed via $img.getSizes(props.src!, …) — the non-null assertion lies, and the helper typically returns a truthy resolved path containing the string "undefined". That makes if (mainSrc.value) truthy and you assign an invalid URL to a real Image(), triggering a network request and an error emit.

Fixing this at the source removes the need for ad‑hoc per-call-site guards (preload, prerender, onMounted) and keeps src.value, imgAttrs.srcset, etc. consistent:

♻️ Suggested centralised fix
-const mainSrc = computed(() =>
-  props.sizes
-    ? sizes.value.src
-    : $img(props.src!, imageModifiers.value, providerOptions.value),
-)
+const mainSrc = computed(() => {
+  if (!props.src) return undefined
+  return props.sizes
+    ? sizes.value.src
+    : $img(props.src, imageModifiers.value, providerOptions.value)
+})

With this in place, the existing if (mainSrc.value) check on line 140 already short‑circuits the img.src assignment, and the inner if (props.sizes) wrap becomes redundant (but harmless). Consider also propagating the guard into the placeholder computed (line 89) which uses props.src! similarly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/NuxtImg.vue` around lines 98 - 147, mainSrc computed
can resolve to a bogus ".../undefined" because it calls $img with props.src! or
uses sizes.value.src which itself was derived from $img.getSizes(props.src!), so
add a central guard: change the mainSrc computation (the mainSrc computed) to
first return null/empty if props.src is falsy (and likewise ensure sizes-based
path only used when props.src is present), and update the placeholder computed
to honor the same props.src guard; this ensures src.value, preload/prerender
usages (href and prerenderStaticImages) and the onMounted Image() assignment
only ever receive a valid URL and removes the need for per-call-site guards.
🧹 Nitpick comments (1)
src/runtime/components/NuxtImg.vue (1)

140-147: Test coverage for the bug fix.

The PR description notes you couldn't get a test working with @nuxt/test-utils. A reasonable test would render <NuxtImg :src="undefined" custom sizes="sm:100vw md:50vw" /> inside a Nuxt test app and assert that:

  • no <link rel="preload" as="image"> is emitted in SSR head,
  • on client mount no Image is constructed with src ending in /undefined,
  • no spurious error event is emitted.

Want me to draft a test/unit (or test/e2e) spec using @nuxt/test-utils's setup() + $fetch/createPage covering these three assertions? I can also open a tracking issue if you'd prefer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/NuxtImg.vue` around lines 140 - 147, Add a test that
renders the NuxtImg component (NuxtImg.vue) with :src="undefined" and attributes
custom sizes="sm:100vw md:50vw" to reproduce the bug: in SSR verify the rendered
head does not include a <link rel="preload" as="image"> for an undefined src
(check server head output), in the client verify that no Image is constructed
with img.src ending in "/undefined" (monitor global Image constructor or inspect
DOM after mount where mainSrc.value would be applied to img.src), and assert
that no spurious "error" event is emitted from the component; target the
behavior around mainSrc.value, img.src, props.sizes and sizes.value to ensure
the fix prevents creating preload links or setting src to "undefined" on mount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/runtime/components/NuxtImg.vue`:
- Around line 106-129: The prerender block calls
prerenderStaticImages(src.value, sizes.value.srcset, useRequestEvent()) even
when props.src is undefined, which can produce garbage URLs; wrap that call with
the same server+src guard used above — e.g., ensure the condition includes
import.meta.server, import.meta.prerender and props.src (or truthiness of
props.src) before invoking prerenderStaticImages so src.value and
sizes.value.srcset are not used when props.src is missing.
- Around line 98-147: mainSrc computed can resolve to a bogus ".../undefined"
because it calls $img with props.src! or uses sizes.value.src which itself was
derived from $img.getSizes(props.src!), so add a central guard: change the
mainSrc computation (the mainSrc computed) to first return null/empty if
props.src is falsy (and likewise ensure sizes-based path only used when
props.src is present), and update the placeholder computed to honor the same
props.src guard; this ensures src.value, preload/prerender usages (href and
prerenderStaticImages) and the onMounted Image() assignment only ever receive a
valid URL and removes the need for per-call-site guards.

---

Nitpick comments:
In `@src/runtime/components/NuxtImg.vue`:
- Around line 140-147: Add a test that renders the NuxtImg component
(NuxtImg.vue) with :src="undefined" and attributes custom sizes="sm:100vw
md:50vw" to reproduce the bug: in SSR verify the rendered head does not include
a <link rel="preload" as="image"> for an undefined src (check server head
output), in the client verify that no Image is constructed with img.src ending
in "/undefined" (monitor global Image constructor or inspect DOM after mount
where mainSrc.value would be applied to img.src), and assert that no spurious
"error" event is emitted from the component; target the behavior around
mainSrc.value, img.src, props.sizes and sizes.value to ensure the fix prevents
creating preload links or setting src to "undefined" on mount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7d96122e-bd3d-4655-bdfd-141c2977032d

📥 Commits

Reviewing files that changed from the base of the PR and between 6ba5f6c and 0d1fbe5.

📒 Files selected for processing (1)
  • src/runtime/components/NuxtImg.vue

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 41.66667% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 33.15%. Comparing base (671e5d0) to head (06c05d4).

Files with missing lines Patch % Lines
src/runtime/components/NuxtImg.vue 44.44% 4 Missing and 1 partial ⚠️
src/runtime/image.ts 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2218      +/-   ##
==========================================
+ Coverage   32.52%   33.15%   +0.62%     
==========================================
  Files           7        7              
  Lines         372      374       +2     
  Branches      131      132       +1     
==========================================
+ Hits          121      124       +3     
+ Misses        194      192       -2     
- Partials       57       58       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Prevent `undefined` from being coerced into strings within `srcset` and `sizes`
- Fix `getSizes` in `image.ts` to return empty values for falsy inputs
- Remove unsafe non-null assertions in `NuxtImg` and `NuxtPicture`
- Add parity test cases for both components to verify `src: undefined` handling
@Bombastickj Bombastickj changed the title fix(NuxtImg): handle src: undefined combined w/ custom: true & sizes fix: handle falsy src in NuxtImg and NuxtPicture May 10, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/runtime/components/NuxtPicture.vue (1)

111-111: ⚡ Quick win

Consider a safer type handling approach.

While props.src as string works at runtime (since getSizes now handles falsy input), the type assertion is misleading when props.src is actually undefined. Consider one of these alternatives:

  1. Guard the call: props.src ? $img.getSizes(props.src, ...) : { srcset: '', sizes: undefined, src: undefined }
  2. Update getSizes signature to accept string | undefined instead of just string

The current implementation works but obscures the intent and could confuse future maintainers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/components/NuxtPicture.vue` at line 111, The call to
$img.getSizes currently forces props.src to string with a type assertion which
hides that props.src can be undefined; update the code to handle the undefined
case explicitly by either guarding the call in NuxtPicture.vue (e.g., call
$img.getSizes only when props.src is truthy and provide a fallback object
otherwise) or change the getSizes function signature to accept string |
undefined so the type reflects reality; locate the call to
$img.getSizes(props.src as string, ...) and modify it to use a runtime guard or
update the getSizes declaration to accept undefined and adjust its
implementation accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/runtime/components/NuxtPicture.vue`:
- Line 111: The call to $img.getSizes currently forces props.src to string with
a type assertion which hides that props.src can be undefined; update the code to
handle the undefined case explicitly by either guarding the call in
NuxtPicture.vue (e.g., call $img.getSizes only when props.src is truthy and
provide a fallback object otherwise) or change the getSizes function signature
to accept string | undefined so the type reflects reality; locate the call to
$img.getSizes(props.src as string, ...) and modify it to use a runtime guard or
update the getSizes declaration to accept undefined and adjust its
implementation accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3af4cc39-a148-4d44-a690-d58419e67b84

📥 Commits

Reviewing files that changed from the base of the PR and between 0d1fbe5 and 82cdf98.

📒 Files selected for processing (5)
  • src/runtime/components/NuxtImg.vue
  • src/runtime/components/NuxtPicture.vue
  • src/runtime/image.ts
  • test/nuxt/image.test.ts
  • test/nuxt/picture.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/runtime/components/NuxtImg.vue

@Bombastickj
Copy link
Copy Markdown
Author

Just pushed a few more updates after a self-review, and I've added comprehensive test cases to ensure the new logic behaves as expected. Let me know if any further changes are needed once someone has the bandwidth to review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants