Describe the bug
experimental.renderBuiltUrl is the documented hook for rewriting emitted asset URLs (e.g. Vercel Skew Protection, which appends ?dpl=<deploymentId> so a long-lived tab keeps loading its original deploy's assets across a new deploy).
When renderBuiltUrl returns a query-suffixed URL for a CSS asset, that URL also lands in the __vite__mapDeps array of any chunk that dynamically imports code-split CSS (host type js). At runtime, the injected preload helper classifies each dep with:
const isCss = dep.endsWith(".css");
...
link.rel = isCss ? "stylesheet" : scriptRel; // scriptRel === "modulepreload"
if (!isCss) link.as = "script";
Because the dep now ends in ?dpl=… rather than .css, isCss is false. The stylesheet gets rel="modulepreload" + as="script", so the browser fetches it expecting a JS module and rejects it:
Failed to load module script: Expected a JavaScript-or-Wasm module script but
the server responded with a MIME type of "text/css". Strict MIME type checking
is enforced for module scripts per HTML spec.
The bytes are served correctly; Vite just requests them as a script. Result: the code-split CSS never applies, and every affected navigation logs the error.
So Vite ships an official API (renderBuiltUrl) that can produce URLs its own runtime preload helper cannot classify.
Expected behavior
A CSS dep should be classified as a stylesheet (rel="stylesheet") regardless of a trailing query string, so renderBuiltUrl-rewritten CSS URLs preload correctly.
Related
Reproduction
https://github.com/emiliosheinz/vite-renderbuilturl-css-preload-repro
Steps to reproduce
vite.config.ts:
import { defineConfig } from 'vite'
export default defineConfig({
experimental: {
renderBuiltUrl(filename, { type }) {
if (type !== 'asset') return undefined
return `/${filename}?dpl=deploy123`
},
},
})
A route that dynamically imports a component with its own CSS:
// main.ts
document.querySelector('#go')!.addEventListener('click', () => {
import('./lazy.ts') // lazy.ts imports './lazy.css'
})
Build and serve dist/. Trigger the dynamic import. The __vite__mapDeps entry for lazy-*.css?dpl=deploy123 is appended as rel="modulepreload", and the console shows the MIME error above.
npm install && npm run build && npm run verify
verify exits 0 when reproduced; npm run preview shows the console error live.
System Info
System:
OS: macOS 26.3
CPU: (14) arm64 Apple M4 Max
Memory: 3.25 GB / 36.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.16.0
Yarn: 4.12.0
npm: 11.13.0
Browsers:
Chrome: 150.0.7871.187
Safari: 26.3
Used Package Manager
npm
Logs
No response
Validations
Describe the bug
experimental.renderBuiltUrlis the documented hook for rewriting emitted asset URLs (e.g. Vercel Skew Protection, which appends?dpl=<deploymentId>so a long-lived tab keeps loading its original deploy's assets across a new deploy).When
renderBuiltUrlreturns a query-suffixed URL for a CSS asset, that URL also lands in the__vite__mapDepsarray of any chunk that dynamically imports code-split CSS (host typejs). At runtime, the injected preload helper classifies each dep with:Because the dep now ends in
?dpl=…rather than.css,isCssisfalse. The stylesheet getsrel="modulepreload"+as="script", so the browser fetches it expecting a JS module and rejects it:The bytes are served correctly; Vite just requests them as a script. Result: the code-split CSS never applies, and every affected navigation logs the error.
So Vite ships an official API (
renderBuiltUrl) that can produce URLs its own runtime preload helper cannot classify.Expected behavior
A CSS dep should be classified as a stylesheet (
rel="stylesheet") regardless of a trailing query string, sorenderBuiltUrl-rewritten CSS URLs preload correctly.Related
base/renderBuiltUrlbehavior differing between CSS and JS (adjacent renderBuiltUrl + CSS area, different symptom)Reproduction
https://github.com/emiliosheinz/vite-renderbuilturl-css-preload-repro
Steps to reproduce
vite.config.ts:A route that dynamically imports a component with its own CSS:
Build and serve
dist/. Trigger the dynamic import. The__vite__mapDepsentry forlazy-*.css?dpl=deploy123is appended asrel="modulepreload", and the console shows the MIME error above.verifyexits 0 when reproduced;npm run previewshows the console error live.System Info
Used Package Manager
npm
Logs
No response
Validations