Astro Info
Astro v6.1.5
Vite v7.3.2
Node v25.9.0
System macOS (arm64)
Package Manager npm
Output static
Adapter @astrojs/cloudflare (v13.1.8)
Integrations @astrojs/starlight (v0.38.3)
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
When @astrojs/cloudflare is configured as the adapter, astro dev serves HTML where Vite's inline <style data-vite-dev-id="..."> blocks are nested inside Starlight's <template id="theme-icons"> element (from ThemeProvider.astro). Because <template> content is inert per the HTML spec, every style block trapped inside is non-functional. The page renders with browser defaults — no Starlight layout, no custom CSS, nothing.
Narrowed to Cloudflare-specific:
- Vanilla Starlight starter (no adapter): template span ≈ 2000 bytes, styles outside the template ✅ works
- Starlight +
@astrojs/node: same — template span ≈ 2000 bytes, styles outside ✅ works
- Starlight +
@astrojs/cloudflare: template span ≈ 99000 bytes, 43 style blocks nested inside the template ❌ broken
Production builds (astro build && astro preview) are unaffected with any adapter, because CSS is extracted to external _astro/*.css files and there's no inline positional injection.
Root cause hypothesis:
ThemeProvider.astro places <template id="theme-icons"> in <head> with three <Icon> children, each an Astro component with scoped CSS. In dev mode, Vite hoists those components' styles and injects them at the current SSR render position. With most adapters (and no adapter at all), the template's open tag, children, and close tag serialize contiguously — style hoisting happens before or after the template boundary. With @astrojs/cloudflare, the SSR stream emits style-hoist injections between the template's opening tag and its first child. Every subsequent style block appends inside the template, and the three SVG icons end up after the styles immediately before the closing </template>.
Verification one-liner (paste into the browser console while viewing any page served by the reproduction):
(async () => {
const t = await fetch(location.pathname).then((r) => r.text());
const tOpen = t.indexOf('<template');
const tClose = t.indexOf('</template>');
console.log({
templateSpan: tClose - tOpen,
stylesInsideTemplate: t.indexOf('<style', tOpen) < tClose,
});
})();
Broken: { templateSpan: ~99000, stylesInsideTemplate: true }.
Working (same repo with the adapter swapped to @astrojs/node or removed): { templateSpan: ~2000, stylesInsideTemplate: false }.
Workaround: astro build && astro preview — HMR is lost but the layout renders correctly.
What's the expected result?
<template id="theme-icons"> should contain only its three <Icon> children (sun/moon/laptop SVGs), the way it does both in production builds and in dev mode with any other adapter. Style blocks should appear before or after the template, not inside it.
Link to Minimal Reproducible Example
https://github.com/p-linnane/starlight-cloudflare-dev-template-bug
Participation
Astro Info
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
When
@astrojs/cloudflareis configured as the adapter,astro devserves HTML where Vite's inline<style data-vite-dev-id="...">blocks are nested inside Starlight's<template id="theme-icons">element (fromThemeProvider.astro). Because<template>content is inert per the HTML spec, every style block trapped inside is non-functional. The page renders with browser defaults — no Starlight layout, no custom CSS, nothing.Narrowed to Cloudflare-specific:
@astrojs/node: same — template span ≈ 2000 bytes, styles outside ✅ works@astrojs/cloudflare: template span ≈ 99000 bytes, 43 style blocks nested inside the template ❌ brokenProduction builds (
astro build && astro preview) are unaffected with any adapter, because CSS is extracted to external_astro/*.cssfiles and there's no inline positional injection.Root cause hypothesis:
ThemeProvider.astroplaces<template id="theme-icons">in<head>with three<Icon>children, each an Astro component with scoped CSS. In dev mode, Vite hoists those components' styles and injects them at the current SSR render position. With most adapters (and no adapter at all), the template's open tag, children, and close tag serialize contiguously — style hoisting happens before or after the template boundary. With@astrojs/cloudflare, the SSR stream emits style-hoist injections between the template's opening tag and its first child. Every subsequent style block appends inside the template, and the three SVG icons end up after the styles immediately before the closing</template>.Verification one-liner (paste into the browser console while viewing any page served by the reproduction):
Broken:
{ templateSpan: ~99000, stylesInsideTemplate: true }.Working (same repo with the adapter swapped to
@astrojs/nodeor removed):{ templateSpan: ~2000, stylesInsideTemplate: false }.Workaround:
astro build && astro preview— HMR is lost but the layout renders correctly.What's the expected result?
<template id="theme-icons">should contain only its three<Icon>children (sun/moon/laptop SVGs), the way it does both in production builds and in dev mode with any other adapter. Style blocks should appear before or after the template, not inside it.Link to Minimal Reproducible Example
https://github.com/p-linnane/starlight-cloudflare-dev-template-bug
Participation