close
Skip to main content
BERJAYA

r/withastro


Anyone else hit these Astro 6 + Cloudflare Workers quirks after upgrading?
Anyone else hit these Astro 6 + Cloudflare Workers quirks after upgrading?

Yooo lads 👋

Been going through a batch of Astro 5 → 6 migrations and a few things kept coming up consistently enough that I figured it was worth writing up. All of these are specifically SSR on Cloudflare Workers with content collections.

Setup: @astrojs/cloudflare v13, output: 'server', glob loader for content.

1. Cannot perform I/O on behalf of a different request

This one catches people off guard because it never existed in Astro 5. workerd enforces strict I/O isolation per request — multiple components calling getCollection() concurrently on the same page and it throws. The tricky part is it's silent in some cases depending on timing.

Fix:

adapter: cloudflare({
  prerenderEnvironment: 'node',
}),

Prerendered pages run in Node, SSR routes hit workerd. It's in the docs but buried. Anyone running this long-term — seen any production edge cases?

2. After fix #1 — Could not import cloudflare:workers crashing dev

Setting prerenderEnvironment: 'node' triggers a secondary issue: the adapter injects its own image transform endpoint that imports cloudflare:workers at startup — which Node doesn't resolve. Crashes the dev server entirely.

The fix is environment-aware image configuration:

adapter: cloudflare({
  imageService: process.env.npm_lifecycle_event === 'build' ? 'compile' : 'passthrough',
  prerenderEnvironment: 'node',
}),

passthrough in dev, full compile (Sharp, AVIF, WebP) in build. This feels like an adapter bug rather than intended behavior — curious if anyone has confirmed that or found a cleaner approach.

3. Astro.request.headers warnings on prerendered routes after migration

This is a migration gotcha — in Astro 5, reading headers in middleware was fine everywhere. In Astro 6, prerendering semantics changed and the middleware now runs during the build phase for prerendered routes, where no actual HTTP request exists.

The pattern that fixes it: gate header access on whether the URL already contains the lang prefix. Prerendered pages always do, so they never need headers:

if (hasLangPrefix) {
  lang = firstSegment as Lang;
} else {
  const cookieLang = context.cookies.get('lang')?.value;
  lang = cookieLang ?? detectBrowserLang(
    context.request.headers.get('accept-language'), ...
  ) as Lang;
}

Worth auditing any middleware that reads headers if you're migrating a multilingual SSR setup.

I've been running through this on 100+ sites at the small studio I work at (webxtek.com). Also had to update astro-stargazer, a component browser we use in dev — minor stuff but needed a version bump after the migration.

Overall: the new workerd-by-default model makes total sense for production parity, but the migration surface is wider than it looks. Happy to dig into any of these if someone's stuck.

What's everyone else seeing?????????????


Arby's real BBQ is real tender
media poster