Skip to content

@sentry/astro: Cloudflare Worker server wrap not applied with @astrojs/cloudflare v14 (transform targets astrojs-ssr-virtual-entry, adapter now emits virtual:cloudflare/worker-entry) #21901

Description

@0x80

Summary

With @astrojs/cloudflare v14, @sentry/astro's server-side init is never applied to the deployed Cloudflare Worker. The Worker's default export is left un-wrapped by withSentry, so server-side error/log capture does not initialize at the fetch boundary — server-side Sentry silently does nothing.

Versions

  • @sentry/astro 10.62.0
  • @sentry/cloudflare 10.62.0
  • @astrojs/cloudflare 14.0.1
  • astro 7.0.3
  • output: 'server', Cloudflare Workers (not Pages)

Root cause

sentryCloudflareVitePlugin (packages/astro/src/integration/cloudflare.ts) wraps the Worker by transforming the module whose id contains astrojs-ssr-virtual-entry:

transform(code, id) {
  if (!id.includes("astrojs-ssr-virtual-entry")) {
    return undefined;
  }
  // ...rewrites `export default X;` -> `export default withSentry(() => undefined, X);`
}

@astrojs/cloudflare v14 builds the Worker through @cloudflare/vite-plugin, whose entry virtual module id is virtual:cloudflare/worker-entry (its generated body ends with export default mod.default ?? {};). The old astrojs-ssr-virtual-entry id is no longer produced, so the transform guard always returns early and the entry is never wrapped.

Effect

With an app configured per the Astro + Cloudflare docs (the sentry() integration in astro.config plus a sentry.server.config.ts doing export default Sentry.withSentry((env) => ({ ... }), handler)), the generated dist/server/entry.mjs default export is the raw Astro handler. Grepping all of dist/server for withSentry(() => finds no match — the plugin's wrap never ran.

The only remaining server wiring is the injected import "sentry.server.config.(ts|js)" (injectScript("page-ssr", ...)). That runs lazily during a page render, so it doesn't deterministically instrument the Worker's fetch entry:

  • errors on the first request (before any page module has evaluated the config) aren't captured;
  • routes that don't pull the page-ssr module graph (e.g. API endpoints) aren't covered;
  • for a Worker whose first traffic is an API route, Sentry.init may never run at all.

Repro

  1. Scaffold an Astro app with output: 'server' and the @astrojs/cloudflare v14 adapter (Workers, not Pages).
  2. npm i @sentry/astro @sentry/cloudflare; add sentry() to integrations; create sentry.server.config.ts per the docs:
    import * as Sentry from '@sentry/cloudflare'
    import handler from '@astrojs/cloudflare/entrypoints/server'
    export default Sentry.withSentry((env) => ({ dsn: env.SENTRY_DSN, enableLogs: true }), handler)
  3. astro build.
  4. Inspect dist/server/entry.mjs: the default export is the un-wrapped Astro handler, and grep -r "withSentry(() =>" dist/server returns nothing.

Suggested fix

Have sentryCloudflareVitePlugin's transform also match the @cloudflare/vite-plugin entry id virtual:cloudflare/worker-entry (whose default-export expression is mod.default ?? {}), in addition to astrojs-ssr-virtual-entry, so the wrap applies on @astrojs/cloudflare v14+.

Workaround

For anyone hitting this before a fix lands: a tiny build-only Vite plugin that transforms the virtual:cloudflare/worker-entry module and wraps its export default mod.default ?? {}; with your own withSentry(optionsCallback, handler) restores deterministic, eager wrapping of the Worker entry.

Metadata

Metadata

Assignees

No fields configured for issues without a type.

Projects

Status
Waiting for: Product Owner

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions