Describe the bug
Per Vite's Static Asset Handling docs, referencing an asset with an absolute path (e.g. <img src="/icon.png">) should never be included in the build — it's meant to be resolved at runtime against the app's public directory, and left completely untouched at build time.
@vitejs/plugin-vue's template.transformAssetUrls option also documents includeAbsolute: false as the default, meaning absolute urls should not be processed into imports.
In practice, a static src="/icon.png" attribute is still converted into import _imports_0 from '/icon.png', and Rollup/Rolldown then tries to resolve it as a real module:
- If no matching file exists anywhere resolvable, the build fails with
[UNRESOLVED_IMPORT] Could not resolve '/icon.png'.
- In a
build.lib + output.preserveModules: true setup (common for component libraries), the same behavior instead surfaces as:
Invalid substitution "/icon" for placeholder "[name]" in "entryFileNames" pattern, can be neither absolute nor relative path.
This happens because the absolute module id ends up being used to compute the [name] placeholder for what Rollup treats as an emitted "entry" chunk (since every module becomes its own chunk under preserveModules).
Expected behavior
The static, absolute src="/icon.png" should be left as a literal string in the compiled render function — no import generated, no attempt to resolve or bundle it — matching both Vite's public-directory convention and @vitejs/plugin-vue's documented includeAbsolute: false default.
Actual behavior
The compiler still emits an import for the absolute path, which either fails to resolve outright, or (in preserveModules library builds) crashes the build with an "Invalid substitution ... entryFileNames" error.
Workaround
Rewriting the static attribute as a bound expression avoids the SFC compiler's asset-url transform (which only processes static string attributes):
-<img src="/icon.png" alt="icon" />
+<img :src="'/icon.png'" alt="icon" />
This works, but shouldn't be necessary given the documented default.
Additional context
Originally hit while migrating a component-library build config from Vite 7 to Vite 8, using build.lib with multiple entries and output.preserveModules: true + preserveModulesRoot: "src". The library references an icon meant to be provided by the consuming application's public directory (src="/icon.png"), which is a standard pattern for component libraries.
If further context is required from the original repository that caused the error during migration, I can also share that here if needed.
Reproduction
https://github.com/JK-Effects/vite-icon-build-bug/tree/main
Steps to reproduce
npm install
npm run build
System Info
System:
OS: Windows 11 10.0.26200
CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
Memory: 492.21 MB / 15.68 GB
Binaries:
Node: 24.16.0 - C:\Program Files\nodejs\node.EXE
npm: 11.18.0 - C:\Users\jkrau\node_modules\.bin\npm.CMD
pnpm: 7.5.0 - C:\Users\jkrau\AppData\Roaming\npm\pnpm.CMD
Browsers:
Edge: Chromium (150.0.4078.48)
Firefox: 152.0.4 - C:\Program Files\Mozilla Firefox\firefox.exe
Internet Explorer: 11.0.26100.8115
npmPackages:
@vitejs/plugin-vue: ^6.0.7 => 6.0.7
vite: ^8.1.3 => 8.1.3
Used Package Manager
npm
Logs
vite v8.1.3 building client environment for production...
✓ 5 modules transformed.
✗ Build failed in 306ms
error during build:
Build failed with 1 error:
[INVALID_OPTION] Invalid substitution "/favicon" for placeholder "[name]" in "entryFileNames" pattern, can be neither absolute nor relative paths.
at aggregateBindingErrorsIntoJsError (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/rolldown/dist/shared/error-BlQ0-ek7.mjs:48:18)
at unwrapBindingResult (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/rolldown/dist/shared/error-BlQ0-ek7.mjs:18:128)
at #build (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/rolldown/dist/shared/rolldown-build-aV0QeeTW.mjs:3256:34)
at async buildEnvironment (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/vite/dist/node/chunks/node.js:32622:66)
at async Object.build (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/vite/dist/node/chunks/node.js:33044:19)
at async Object.buildApp (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/vite/dist/node/chunks/node.js:33041:153)
at async CAC.<anonymous> (file:///C:/Users/jkrau/Downloads/vite-repro/vite-repro/node_modules/vite/dist/node/cli.js:777:3) {
errors: [Getter/Setter]
}
Validations
Describe the bug
Per Vite's Static Asset Handling docs, referencing an asset with an absolute path (e.g.
<img src="/icon.png">) should never be included in the build — it's meant to be resolved at runtime against the app'spublicdirectory, and left completely untouched at build time.@vitejs/plugin-vue's template.transformAssetUrlsoption also documentsincludeAbsolute: falseas the default, meaning absolute urls should not be processed into imports.In practice, a static
src="/icon.png"attribute is still converted intoimport _imports_0 from '/icon.png', and Rollup/Rolldown then tries to resolve it as a real module:[UNRESOLVED_IMPORT] Could not resolve '/icon.png'.build.lib + output.preserveModules: truesetup (common for component libraries), the same behavior instead surfaces as:This happens because the absolute module id ends up being used to compute the
[name]placeholder for what Rollup treats as an emitted "entry" chunk (since every module becomes its own chunk underpreserveModules).Expected behavior
The static, absolute
src="/icon.png"should be left as a literal string in the compiled render function — no import generated, no attempt to resolve or bundle it — matching both Vite's public-directory convention and@vitejs/plugin-vue'sdocumentedincludeAbsolute: falsedefault.Actual behavior
The compiler still emits an import for the absolute path, which either fails to resolve outright, or (in
preserveModuleslibrary builds) crashes the build with an "Invalid substitution ... entryFileNames" error.Workaround
Rewriting the static attribute as a bound expression avoids the SFC compiler's asset-url transform (which only processes static string attributes):
This works, but shouldn't be necessary given the documented default.
Additional context
Originally hit while migrating a component-library build config from Vite 7 to Vite 8, using
build.libwith multiple entries andoutput.preserveModules: true + preserveModulesRoot: "src". The library references an icon meant to be provided by the consuming application'spublicdirectory (src="/icon.png"), which is a standard pattern for component libraries.If further context is required from the original repository that caused the error during migration, I can also share that here if needed.
Reproduction
https://github.com/JK-Effects/vite-icon-build-bug/tree/main
Steps to reproduce
npm install
npm run build
System Info
System: OS: Windows 11 10.0.26200 CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz Memory: 492.21 MB / 15.68 GB Binaries: Node: 24.16.0 - C:\Program Files\nodejs\node.EXE npm: 11.18.0 - C:\Users\jkrau\node_modules\.bin\npm.CMD pnpm: 7.5.0 - C:\Users\jkrau\AppData\Roaming\npm\pnpm.CMD Browsers: Edge: Chromium (150.0.4078.48) Firefox: 152.0.4 - C:\Program Files\Mozilla Firefox\firefox.exe Internet Explorer: 11.0.26100.8115 npmPackages: @vitejs/plugin-vue: ^6.0.7 => 6.0.7 vite: ^8.1.3 => 8.1.3Used Package Manager
npm
Logs
vite v8.1.3 building client environment for production...
✓ 5 modules transformed.
✗ Build failed in 306ms
error during build:
Build failed with 1 error:
[INVALID_OPTION] Invalid substitution "/favicon" for placeholder "[name]" in "entryFileNames" pattern, can be neither absolute nor relative paths.
errors: [Getter/Setter]
}
Validations