Skip to content

feat(nuxt): Add Cloudflare Nitro plugin #15597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jun 30, 2025
Merged

feat(nuxt): Add Cloudflare Nitro plugin #15597

merged 22 commits into from
Jun 30, 2025

Conversation

s1gr1d
Copy link
Member

@s1gr1d s1gr1d commented Mar 5, 2025

A Nitro plugin which initializes Sentry when deployed to Cloudflare.

  1. Add @sentry/cloudflare as additional dependency (same version as @sentry/nuxt)

  2. Remove the previous server config file: sentry.server.config.ts

  3. Add a plugin in server/plugins (e.g. server/plugins/sentry-cloudflare-setup.ts)

  4. Add this code in your plugin file

    // server/plugins/sentry-cloudflare-setup.ts (filename does not matter)
    import { sentryCloudflareNitroPlugin } from '@sentry/nuxt/module/plugins'
    
    export default defineNitroPlugin(sentryCloudflareNitroPlugin({
        dsn: 'https://dsn',
        tracesSampleRate: 1.0,
    }))

    or with access to nitroApp:

    export default defineNitroPlugin(sentryCloudflareNitroPlugin((nitroApp: NitroApp) => {
      return  ({
        dsn: 'https://dsn',
        tracesSampleRate: 1.0,
      })
    }))

@s1gr1d s1gr1d force-pushed the sig/nuxt-cloudflare branch from 243b681 to 0800086 Compare June 23, 2025 10:50
*
* @default false
*/
continueTraceFromPropagationContext?: boolean;
Copy link
Member Author

@s1gr1d s1gr1d Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbhiPrasad Is this a good place for adding the option? As it's only needed in the requestHandler, I added it only here and not in the general Cloudflare SDK options.

Actually, I don't need this option because it would always take same trace ID from the Cloudflare environment which leads to multiple pageload spans within a trace as they all have the same trace ID in the meta tags.

@s1gr1d s1gr1d marked this pull request as ready for review June 26, 2025 12:47
Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick drive by, I'll review in more detail tomorrow

if (traceData && Object.keys(traceData).length > 0) {
// Storing trace data in the event context for later use in HTML meta-tags (enables correct connection of parent/child span relationships)
// @ts-expect-error Storing a new key in the event context
event.context[TRACE_DATA_KEY] = traceData;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of attaching to event.context, can we instead track this via a WeakMap?

So we hold a reference to event.context or event.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean using the event as a key?

That's a bit tricky as the event in localFetch is a full Cloudflare event which A LOT of data. And the event in the render:html hook is only a string (e.g. "[GET] /"). So I cannot get the value out of the WeakMap anymore, as I don't have a key anymore. Or would you have an idea on how to do that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that context.cf is available in both and looks the same. Using a WeakMap now: a20ebef

cursor[bot]

This comment was marked as outdated.

Copy link

codecov bot commented Jun 27, 2025

❌ Unsupported file format

Upload processing failed due to unsupported file format. Please review the parser error message:
Error deserializing json

Caused by:
expected value at line 1 column 1

For more help, visit our troubleshooting guide.

cursor[bot]

This comment was marked as outdated.

@s1gr1d s1gr1d requested a review from AbhiPrasad June 27, 2025 10:48
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Sentry Plugin Fails to Inject Trace Data

The sentryCloudflareNitroPlugin fails to inject Sentry trace data into HTML meta tags. This is due to a WeakMap key mismatch: trace data is stored using event.context.cf from a CfEventType in nitroApp.localFetch, but the render:html hook attempts retrieval using event.context.cf from an H3Event. The H3Event's context.cf is either not correctly provided by nitroApp or is a different object instance, preventing the WeakMap lookup from succeeding.

packages/nuxt/src/runtime/plugins/sentry-cloudflare.server.ts#L89-L152

(nitroApp: NitroApp): void => {
const traceDataMap = new WeakMap<object, ReturnType<typeof getTraceData>>();
nitroApp.localFetch = new Proxy(nitroApp.localFetch, {
async apply(handlerTarget, handlerThisArg, handlerArgs: [string, unknown]) {
setAsyncLocalStorageAsyncContextStrategy();
const cloudflareOptions = typeof optionsOrFn === 'function' ? optionsOrFn(nitroApp) : optionsOrFn;
const pathname = handlerArgs[0];
const event = handlerArgs[1];
if (!isEventType(event)) {
logger.log("Nitro Cloudflare plugin did not detect a Cloudflare event type. Won't patch Cloudflare handler.");
return handlerTarget.apply(handlerThisArg, handlerArgs);
} else {
// Usually, the protocol already includes ":"
const url = `${event.protocol}${event.protocol.endsWith(':') ? '' : ':'}//${event.host}${pathname}`;
const request = new Request(url, {
method: event.method,
headers: event.headers,
cf: event.context.cf,
}) as Request<unknown, IncomingRequestCfProperties<unknown>>;
const requestHandlerOptions = {
options: cloudflareOptions,
request,
context: event.context.cloudflare.context,
};
return wrapRequestHandler(requestHandlerOptions, () => {
const isolationScope = getIsolationScope();
const newIsolationScope =
isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope;
const traceData = getTraceData();
if (traceData && Object.keys(traceData).length > 0) {
// Storing trace data in the WeakMap using event.context.cf as key for later use in HTML meta-tags
traceDataMap.set(event.context.cf, traceData);
logger.log('Stored trace data for later use in HTML meta-tags: ', traceData);
}
logger.log(
`Patched Cloudflare handler (\`nitroApp.localFetch\`). ${
isolationScope === newIsolationScope ? 'Using existing' : 'Created new'
} isolation scope.`,
);
return handlerTarget.apply(handlerThisArg, handlerArgs);
});
}
},
});
// @ts-expect-error - 'render:html' is a valid hook name in the Nuxt context
nitroApp.hooks.hook('render:html', (html: NuxtRenderHTMLContext, { event }: { event: H3Event }) => {
const storedTraceData = event?.context?.cf ? traceDataMap.get(event.context.cf) : undefined;
if (storedTraceData && Object.keys(storedTraceData).length > 0) {
logger.log('Using stored trace data for HTML meta-tags: ', storedTraceData);
addSentryTracingMetaTags(html.head, storedTraceData);
} else {
addSentryTracingMetaTags(html.head);
}
});

Fix in Cursor


Bug: Meta Tag Check Fails on Non-String Elements

The addSentryTracingMetaTags function's duplicate meta tag check incorrectly assumes all head array elements are strings, leading to runtime errors when tag.includes() is called on non-string types. Furthermore, the string-based detection (tag.includes('meta') && tag.includes('sentry-trace')) is fragile and can produce false positives, preventing legitimate meta tags from being added.

packages/nuxt/src/runtime/utils.ts#L46-L47

if (head.some(tag => tag.includes('meta') && tag.includes('sentry-trace'))) {

Fix in Cursor


Was this report helpful? Give feedback by reacting with 👍 or 👎

@s1gr1d
Copy link
Member Author

s1gr1d commented Jun 27, 2025

Addition to the Cursor comments:

  • Although the type is different (H3Event and CfEventType), both share the same context.cf property.
  • The meta tags are always strings. It's based on the type definition of Nuxt.

return handlerTarget.apply(handlerThisArg, handlerArgs);
} else {
// Usually, the protocol already includes ":"
const url = `${event.protocol}${event.protocol.endsWith(':') ? '' : ':'}//${event.host}${pathname}`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it doesn't seem like we are using pathname more than once, we may as well use the handler argument directly 😉

Suggested change
const url = `${event.protocol}${event.protocol.endsWith(':') ? '' : ':'}//${event.host}${pathname}`;
const url = `${event.protocol}${event.protocol.endsWith(':') ? '' : ':'}//${event.host}${handlerArgs[0]}`;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's very valid but I added the variables on purpose so it's easier to read and understand :)
It's not very clear that this first array element is the pathname and with the variable it's more obvious.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally get you here.
It's especially relevant in case we want to use the path name in the future. Again I was just being nitpicky here 😁

Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

@s1gr1d s1gr1d merged commit bc6725c into develop Jun 30, 2025
162 of 165 checks passed
@s1gr1d s1gr1d deleted the sig/nuxt-cloudflare branch June 30, 2025 15:17
s1gr1d added a commit to getsentry/sentry-docs that referenced this pull request Jul 1, 2025
## DESCRIBE YOUR PR
Docs for getsentry/sentry-javascript#15597

closes getsentry/sentry-javascript#16780
## IS YOUR CHANGE URGENT?  

Help us prioritize incoming PRs by letting us know when the change needs
to go live.
- [ ] Urgent deadline (GA date, etc.): <!-- ENTER DATE HERE -->
- [ ] Other deadline: <!-- ENTER DATE HERE -->
- [ ] None: Not urgent, can wait up to 1 week+

## SLA

- Teamwork makes the dream work, so please add a reviewer to your PRs.
- Please give the docs team up to 1 week to review your PR unless you've
added an urgent due date to it.
Thanks in advance for your help!

## PRE-MERGE CHECKLIST

*Make sure you've checked the following before merging your changes:*

- [ ] Checked Vercel preview for correctness, including links
- [ ] PR was reviewed and approved by any necessary SMEs (subject matter
experts)
- [ ] PR was reviewed and approved by a member of the [Sentry docs
team](https://github.com/orgs/getsentry/teams/docs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants