Skip to content

duplicate-object-key warning: provider key written twice in generateImageOptions output #2242

@wuservices

Description

@wuservices

I noticed a warning in my build output, and Claude traced it to the Nuxt Image dependency. Cleaning on the warning would be nice, but this shouldn't have a runtime impact.

Claude investigation below.

Environment

  • @nuxt/image 2.0.0
  • Reproduced with Nuxt 4, default config, no user-provided image options.
  • Generated bundle contains the duplicate regardless of bundler; the warning is surfaced when a downstream pass (e.g. wrangler deploy's esbuild step) re-parses the bundled output. nuxt build itself does not print the warning — verified with a minimal repro.

Symptom

During wrangler deploy (or any downstream esbuild parse of the Nitro output):

▲ [WARNING] Duplicate key "provider" in object literal [duplicate-object-key]

    .output/server/chunks/build/server.mjs:<line>:<col>:
      35 │ ...ities:[1,2],format:["webp"],provider:"none",providers:{none:{...
         ╵                                ~~~~~~~~

  The original key "provider" is here:

    .output/server/chunks/build/server.mjs:<line>:<col>:
      35 │ ...1280,"2xl":1536},presets:{},provider:"none",domains:[],alias:{}...
         ╵                                ~~~~~~~~

Direct inspection of the Nitro bundle confirms two provider: keys in the same imageOptions object literal:

$ grep -c 'provider:"none"' .output/server/chunks/build/server.mjs
# 2 occurrences inside the same literal (single line); see context below
$ python3 -c "import re,sys; s=open('.output/server/chunks/build/server.mjs').read(); ps=[m.start() for m in re.finditer(r'provider:\"none\"', s)]; print(len(ps)); [print(s[max(0,p-80):p+80]) for p in ps]"
2
...d 0},r)}const pe={screens:{sm:640,...,presets:{},provider:"none",domains:[],alias:{},densities:[1,2],format:["webp"],provider:"none",providers:{none:{...
,presets:{},provider:"none",domains:[],alias:{},densities:[1,2],format:["webp"],provider:"none",providers:{none:{setup:function(e)...

Root cause

src/module.ts on main, two locations:

L117–124imageOptions is built via pick(options, [...]) which already includes 'provider':

const imageOptions: Omit<CreateImageOptions, 'providers' | 'nuxt' | 'runtimeConfig'> = pick(options, [
  'screens',
  'presets',
  'provider',   // ← included here
  'domains',
  'alias',
  'densities',
  'format',
  'quality',
])

L139–151generateImageOptions spreads imageOptions (which carries provider) and writes provider explicitly on the next line:

export const imageOptions = {
  ...${JSON.stringify(imageOptions, null, 2)},   // ← spreads provider
  /** @type {${JSON.stringify(imageOptions.provider)}} */
  provider: ${JSON.stringify(imageOptions.provider)},   // ← second write of provider
  providers: { ... }
}

When bundled, the spread is flattened by esbuild because its RHS is a literal object known at compile time. The result: two provider: keys side-by-side in the same object literal. Runtime is unaffected (last-key-wins, same value), but esbuild's duplicate-object-key rule fires when it parses the output.

Reproduction

mkdir nuxt-image-repro && cd nuxt-image-repro
cat > package.json <<'EOF'
{
  "name": "nuxt-image-repro",
  "private": true,
  "type": "module",
  "scripts": { "build": "nuxt build" },
  "devDependencies": {
    "nuxt": "^4.0.0",
    "@nuxt/image": "2.0.0",
    "wrangler": "*"
  }
}
EOF
cat > nuxt.config.ts <<'EOF'
export default defineNuxtConfig({ modules: ['@nuxt/image'] })
EOF
mkdir -p app && cat > app/app.vue <<'EOF'
<template><NuxtImg src="/test.png" width="100" height="100" /></template>
EOF
cat > wrangler.jsonc <<'EOF'
{
  "name": "nuxt-image-repro",
  "compatibility_date": "2026-02-24",
  "compatibility_flags": ["nodejs_compat"],
  "main": ".output/server/index.mjs",
  "assets": { "binding": "ASSETS", "directory": ".output/public" }
}
EOF
pnpm install
NITRO_PRESET=cloudflare-module pnpm exec nuxt build
pnpm exec wrangler deploy --dry-run --outdir wrangler-out
# → warning is printed during the wrangler step

Or, without wrangler, observe the duplicate directly in the build output:

NITRO_PRESET=cloudflare-module pnpm exec nuxt build
grep -c 'provider:"none"' .output/server/chunks/build/server.mjs   # → 2

A <NuxtImg> (or any consumer) is required — without it, tree-shaking drops imageOptions from the bundle and the duplicate isn't present in .output.

Proposed fix

Remove 'provider' from the pick() array. The explicit emission keeps the @type JSDoc that lets editors infer the literal-string type of provider in the generated bundle; the spread doesn't carry that annotation, so the explicit assignment should stay.

--- a/src/module.ts
+++ b/src/module.ts
@@ -117,7 +117,6 @@
     const imageOptions: Omit<CreateImageOptions, 'providers' | 'nuxt' | 'runtimeConfig'> = pick(options, [
       'screens',
       'presets',
-      'provider',
       'domains',
       'alias',
       'densities',

Alternative: drop the explicit provider: line and rely on the spread, but that loses the @type annotation. The pick-side change is preferable.

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