Skip to content

Bug: CSS assetsPrefix with remote URLs incorrectly prepends forward slash #15252

@lamalex

Description

@lamalex

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

  1. Create an Astro project with content collections (MDX files)
  2. Configure astro.config.ts with a remote assetsPrefix:
export default defineConfig({
  build: {
    assetsPrefix: {
      js: 'https://cdn.example.com',
      css: 'https://cdn.example.com',
    },
  },
});
  1. Build the project: astro build
  2. 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

  • I am willing to submit a pull request for this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageIssue needs to be triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions