-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Dev server hangs on first request with Cloudflare adapter: circular deadlock in ensureModulesLoaded() (regression from #15988) #16010
Description
Astro Info
Astro v6.0.7
Node v22.x
System macOS (arm64)
Package Manager bun
Output server
Adapter @astrojs/cloudflare v13
Describe the Bug
After upgrading to 6.0.7, the dev server (astro dev with @astrojs/cloudflare) hangs permanently on the first HTTP request. The server starts successfully and prints the ready URL, but every request times out — no response is ever returned. The issue does not occur on 6.0.6.
This is a regression introduced by PR #15988, which added ensureModulesLoaded() to vite-plugin-css/index.js.
Root Cause
ensureModulesLoaded() is called from the load handler of virtual:astro:dev-css:X (a per-route CSS collector module). It walks the full SSR module graph starting from the corresponding virtual:astro:page:X module. The graph contains virtual:astro:dev-css-all because pipeline.js imports it with a static string literal:
const { devCSSMap } = await import("virtual:astro:dev-css-all");Vite tracks static dynamic imports in importedModules, so virtual:astro:dev-css-all always appears in the graph. That module's importedModules in turn contains all per-route virtual:astro:dev-css:*.astro modules (because virtual:astro:dev-css-all statically imports them all via the generated map).
When ensureModulesLoaded() reaches one of those virtual:astro:dev-css:Y modules and finds it lacks a transformResult, it calls:
await env.fetchModule(imp.id); // imp.id = "virtual:astro:dev-css:Y"But virtual:astro:dev-css:Y's own load handler is already running (its ensureModulesLoaded call is in progress). Vite's _pendingRequests map causes the second fetchModule call to wait for the first to complete — which can never happen because both are waiting on each other. Permanent deadlock.
This is specific to the Cloudflare adapter because workerd requests modules concurrently via __VITE_INVOKE_MODULE__, making it likely that multiple virtual:astro:dev-css:* load handlers run simultaneously.
Fix
Inside ensureModulesLoaded(), skip virtual:astro:dev-css:* and virtual:astro:dev-css-all modules to break the cycle:
async function ensureModulesLoaded(env, mod, seen = new Set()) {
const id = mod.id ?? mod.url;
if (seen.has(id)) return;
seen.add(id);
for (const imp of mod.importedModules) {
if (!imp.id) continue;
if (seen.has(imp.id)) continue;
if (imp.id.includes(PROPAGATED_ASSET_QUERY_PARAM)) continue;
// Skip virtual dev-css modules to prevent circular deadlocks:
// these collector modules reference each other via the dev-css-all map,
// causing fetchModule calls on modules currently being transformed.
if (
imp.id.startsWith(RESOLVED_MODULE_DEV_CSS_PREFIX) ||
imp.id === RESOLVED_MODULE_DEV_CSS ||
imp.id === RESOLVED_MODULE_DEV_CSS_ALL
) continue;
if (!imp.transformResult) {
try {
await env.fetchModule(imp.id);
} catch {}
}
await ensureModulesLoaded(env, imp, seen);
}
}This is safe because the virtual dev-css modules are CSS collectors — they don't contain CSS themselves. Skipping them during the pre-warm traversal has no effect on CSS injection; collectCSSWithOrder() handles the actual CSS gathering separately.
What's the expected result?
Dev server responds to requests normally, as it did in 6.0.6.
Steps to Reproduce
- Use
@astrojs/cloudflarev13 adapter with a moderately large project (multiple routes, third-party integrations that add SSR modules) - Run
astro dev - Open any page — the request hangs indefinitely
Projects with small SSR module graphs may not hit this because ensureModulesLoaded needs to traverse deep enough to reach virtual:astro:dev-css-all before the deadlock triggers. Adding packages excluded from optimizeDeps (like @sentry/astro) increases graph size and makes the issue reliably reproducible.
Link to Minimal Reproducible Example
No minimal repro yet, but the fix above applied to node_modules/astro/dist/vite-plugin-css/index.js resolves the issue completely.
Participation
- I am willing to submit a pull request for this issue.