Astro Info
Astro v5.16.11
Vite v6.4.1
Node v24.10.0
System macOS (arm64)
Package Manager npm
Output static
Adapter @astrojs/node (v9.5.1)
Integrations event-validation-integration
download-vendor-scripts
nonce-generator
sri-integrity
@astrojs/react (v4.4.2)
@astrojs/sitemap (v3.6.1)
@astrojs/mdx (v4.3.13)
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
Description
When using build.assetsPrefix with a remote URL (e.g., https://cdn.example.com) for CSS assets, the generated <link> elements have an incorrect href attribute with a leading / prepended to the full URL.
Expected: <link href="https://cdn.example.com/assets/style.css" rel="stylesheet">
Actual: <link href="/https://cdn.example.com/assets/style.css" rel="stylesheet">
This only affects CSS assets. JavaScript assets with the same assetsPrefix configuration work correctly.
Reproduction
Minimal reproduction
- Create an Astro project with content collections (MDX files)
- Configure
astro.config.ts with a remote assetsPrefix:
export default defineConfig({
build: {
assetsPrefix: {
js: 'https://cdn.example.com',
css: 'https://cdn.example.com',
},
},
});
- Build the project:
astro build
- Inspect the generated HTML files
Expected behavior
CSS links should use the full CDN URL without modification:
<link href="https://cdn.example.com/_astro/style.css" rel="stylesheet">
Actual behavior
CSS links have a / prepended to the URL:
<link href="/https://cdn.example.com/_astro/style.css" rel="stylesheet">
Root Cause Analysis
The bug is in packages/astro/src/content/runtime.ts (compiled to dist/content/runtime.js).
In the createComponent factory function that handles content collection rendering, external stylesheets are processed at approximately line 590:
if (Array.isArray(collectedLinks)) {
links = collectedLinks.map((link) => {
return renderUniqueStylesheet(result, {
type: "external",
src: prependForwardSlash(link) // BUG: Always prepends "/" even for remote URLs
});
}).join("");
}
The prependForwardSlash(link) call unconditionally adds a / prefix, even when link is already a full URL with a protocol (http/https).
Why scripts work correctly
The build-time code in vite-plugin-content-assets.js correctly handles the assetsPrefix:
const prependBase = (src) => {
const { assetsPrefix } = options.settings.config.build;
if (assetsPrefix) {
const fileExtension = extname(src);
const pf = getAssetsPrefix(fileExtension, assetsPrefix);
return joinPaths(pf, src); // Correctly joins without extra "/"
} else {
return prependForwardSlash(joinPaths(options.settings.config.base, src));
}
};
This prependBase function is applied at build time when replacing LINKS_PLACEHOLDER. However, the runtime code then applies prependForwardSlash again, breaking remote URLs.
Proposed Fix
Check if the link is a remote URL before prepending the forward slash. The isRemotePath utility from @astrojs/internal-helpers/path can be used:
// Before (buggy):
src: prependForwardSlash(link)
// After (fixed):
src: isRemotePath(link) ? link : prependForwardSlash(link)
The isRemotePath function already exists in the codebase and correctly identifies URLs with protocols.
Impact
This bug prevents using CDN URLs for CSS assets, which is a common use case for:
- Serving static assets from a CDN for performance
- Implementing CDN fallback patterns
- Multi-region deployments with regional CDNs
Workaround
Currently, the only workaround is to patch the Astro package directly or use a forked version.
What's the expected result?
Expected behavior
CSS links should use the full CDN URL without modification:
<link href="https://cdn.example.com/_astro/style.css" rel="stylesheet">
Link to Minimal Reproducible Example
https://stackblitz.com/github/lamalex/astro-css-assetsprefix-bug
Participation
Astro Info
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
Description
When using
build.assetsPrefixwith a remote URL (e.g.,https://cdn.example.com) for CSS assets, the generated<link>elements have an incorrecthrefattribute with a leading/prepended to the full URL.Expected:
<link href="https://cdn.example.com/assets/style.css" rel="stylesheet">Actual:
<link href="/https://cdn.example.com/assets/style.css" rel="stylesheet">This only affects CSS assets. JavaScript assets with the same
assetsPrefixconfiguration work correctly.Reproduction
Minimal reproduction
astro.config.tswith a remoteassetsPrefix:astro buildExpected behavior
CSS links should use the full CDN URL without modification:
Actual behavior
CSS links have a
/prepended to the URL:Root Cause Analysis
The bug is in
packages/astro/src/content/runtime.ts(compiled todist/content/runtime.js).In the
createComponentfactory function that handles content collection rendering, external stylesheets are processed at approximately line 590:The
prependForwardSlash(link)call unconditionally adds a/prefix, even whenlinkis already a full URL with a protocol (http/https).Why scripts work correctly
The build-time code in
vite-plugin-content-assets.jscorrectly handles theassetsPrefix:This
prependBasefunction is applied at build time when replacingLINKS_PLACEHOLDER. However, the runtime code then appliesprependForwardSlashagain, breaking remote URLs.Proposed Fix
Check if the link is a remote URL before prepending the forward slash. The
isRemotePathutility from@astrojs/internal-helpers/pathcan be used:The
isRemotePathfunction already exists in the codebase and correctly identifies URLs with protocols.Impact
This bug prevents using CDN URLs for CSS assets, which is a common use case for:
Workaround
Currently, the only workaround is to patch the Astro package directly or use a forked version.
What's the expected result?
Expected behavior
CSS links should use the full CDN URL without modification:
Link to Minimal Reproducible Example
https://stackblitz.com/github/lamalex/astro-css-assetsprefix-bug
Participation