The bug
The vercel provider writes a fixed minimumCacheTTL: 60 * 5 (300 seconds, 5 minutes) into the generated Vercel build output. Unlike the sibling fields domains, sizes, and formats, which read from moduleOptions or providerOptions.options, minimumCacheTTL is a literal. There is no image.providers.vercel.options.minimumCacheTTL or any module-level option to change it.
After nuxi build with the vercel preset, .vercel/output/config.json contains:
images.minimumCacheTTL = 300
The only user-side escape hatch is reaching past the module and overriding nitro.vercel.config.images.minimumCacheTTL directly, which bypasses the provider's defu merge for the other image fields and is a regression risk for any future change to the provider.
This matters because the Vercel image optimizer cache TTL is the floor for remote-image cache invalidation. A 5 minute floor is far too low for remote sources that change rarely (GitHub repo covers, music artwork, and similar), so every cache miss hits upstream and counts against the image transformation quota. Vercel recommends tuning this per project, and local images on the same platform default to 31 days.
To reproduce
https://stackblitz.com/github/JonathanXDR/repro-nuxt-image-vercel-minimum-cache-ttl
Expected behavior
@nuxt/image exposes a typed option such as image.providers.vercel.options.minimumCacheTTL (or a shared image.minimumCacheTTL) that flows through the same defu merge as domains, sizes, and formats, so consumers can raise the floor without reaching into nitro.vercel.config.
Additional context
Root cause is in the installed package, node_modules/@nuxt/image/dist/module.js (v2.0.0):
// module.js:113
vercel(providerOptions, moduleOptions, nuxt) {
nuxt.options.nitro = defu(nuxt.options.nitro, {
vercel: {
config: {
images: {
domains: moduleOptions.domains,
minimumCacheTTL: 60 * 5, // module.js:119, hard-coded
sizes: Array.from(new Set(Object.values(moduleOptions.screens || {}))),
formats: providerOptions.options?.formats ?? ["image/webp", "image/avif"]
}
}
}
});
}
The adjacent awsAmplify provider in the same file has the identical hard-coding at module.js:137 (minimumCacheTTL: 60 * 5), so a generalised fix would cover both providers.
User-side workaround that currently sticks but skips the module merge path:
// nuxt.config.ts
nitro: {
vercel: {
config: {
images: {
minimumCacheTTL: 60 * 60 * 24 * 28, // 28 days
},
},
},
}
|
|
| Operating System |
Darwin |
| Node Version |
v24.18.0 |
| Nuxt Version |
4.4.6 |
| CLI Version |
3.36.0 |
| Nitro Version |
2.13.4 |
| Package Manager |
npm@11.17.0 |
| Builder |
vite |
| User Config |
compatibilityDate, modules, nitro, image |
| Runtime Modules |
@nuxt/image@2.0.0 |
| Build Modules |
- |
Verified native-free with a clean npm install plus npm run build (the proof is a generated config value, no image serving or native sharp needed).
The bug
The
vercelprovider writes a fixedminimumCacheTTL: 60 * 5(300 seconds, 5 minutes) into the generated Vercel build output. Unlike the sibling fieldsdomains,sizes, andformats, which read frommoduleOptionsorproviderOptions.options,minimumCacheTTLis a literal. There is noimage.providers.vercel.options.minimumCacheTTLor any module-level option to change it.After
nuxi buildwith thevercelpreset,.vercel/output/config.jsoncontains:The only user-side escape hatch is reaching past the module and overriding
nitro.vercel.config.images.minimumCacheTTLdirectly, which bypasses the provider'sdefumerge for the other image fields and is a regression risk for any future change to the provider.This matters because the Vercel image optimizer cache TTL is the floor for remote-image cache invalidation. A 5 minute floor is far too low for remote sources that change rarely (GitHub repo covers, music artwork, and similar), so every cache miss hits upstream and counts against the image transformation quota. Vercel recommends tuning this per project, and local images on the same platform default to 31 days.
To reproduce
https://stackblitz.com/github/JonathanXDR/repro-nuxt-image-vercel-minimum-cache-ttl
Expected behavior
@nuxt/imageexposes a typed option such asimage.providers.vercel.options.minimumCacheTTL(or a sharedimage.minimumCacheTTL) that flows through the samedefumerge asdomains,sizes, andformats, so consumers can raise the floor without reaching intonitro.vercel.config.Additional context
Root cause is in the installed package,
node_modules/@nuxt/image/dist/module.js(v2.0.0):The adjacent
awsAmplifyprovider in the same file has the identical hard-coding atmodule.js:137(minimumCacheTTL: 60 * 5), so a generalised fix would cover both providers.User-side workaround that currently sticks but skips the module merge path:
Darwinv24.18.04.4.63.36.02.13.4npm@11.17.0vitecompatibilityDate,modules,nitro,image@nuxt/image@2.0.0-Verified native-free with a clean
npm installplusnpm run build(the proof is a generated config value, no image serving or native sharp needed).