Skip to content

wrapFetchWithSentry's injected trace meta tags break React 19 document hydration (error #418 on every SSR page) #21915

Description

@zenato

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/tanstackstart-react

SDK Version

10.63.0

Framework Version

React 19.2.4, @tanstack/react-start 1.168.26 (Vite 8 + Nitro)

Link to Sentry event

No response

Reproduction Example/SDK Setup

Standard TanStack Start setup per the SDK docs — the relevant part is the server entry:

// src/server.ts
import '../instrument.server.mjs'
import { wrapFetchWithSentry } from '@sentry/tanstackstart-react'
import handler, { createServerEntry } from '@tanstack/react-start/server-entry'

export default createServerEntry(
  wrapFetchWithSentry({
    fetch(request: Request) {
      return handler.fetch(request)
    },
  }),
)
// instrument.server.mjs
import * as Sentry from '@sentry/tanstackstart-react'
Sentry.init({
  dsn: process.env.VITE_SENTRY_DSN,
  tracesSampleRate: 0,
})

Client entry is the TanStack Start default (whole-document hydration):

// src/client.tsx
hydrateRoot(
  document,
  <StrictMode>
    <StartClient />
  </StrictMode>,
)

Steps to Reproduce

  1. Create a TanStack Start (React 19) app and set up @sentry/tanstackstart-react >= 10.55.0 per the docs, including wrapFetchWithSentry in the server entry.
  2. Make a production build and run it with a server-side DSN configured (any syntactically valid DSN reproduces it — the SDK just needs to be enabled).
  3. Open any SSR page with the browser console open.

What happens: wrapFetchWithSentryinjectMetaTagsInResponse inserts getTraceMetaTags() right after <head>. getTraceMetaTags() joins the two meta tags with '\n' (packages/core/src/utils/meta.ts), so the served HTML is:

<head><meta name="sentry-trace" content="..."/>
<meta name="baggage" content="..."/><meta charSet="utf-8"/>...

That newline becomes a whitespace text node inside <head>. React 19's whole-document hydration tolerates unexpected elements in <head>/<body>, but not unexpected text nodes — hydration fails with error #418 and the entire document is re-rendered on the client.

I isolated this on a production deployment by editing the served HTML and re-loading it in place (document.open()/write()/close()), same environment for all variants:

served HTML variant result
original (both metas + \n between them) #418 on every load
both meta tags removed no error
metas kept, only the \n between them removed no error
metas removed entirely, a bare \n inserted after <head> #418

So the text node is both necessary and sufficient; the meta elements themselves are fine. Not reproducible in dev (SDK disabled → no injection), which is presumably why this hasn't been reported — it only shows in deployed apps.

Introduced with the meta tag injection in #21144 (10.55.0). Still present in 10.63.0. Possibly the same phenomenon as the "benign React #418" mentioned in passing in #21770.

Suggested fix: join the tags without the newline in getTraceMetaTags() (or inject without whitespace in addMetaTagToHead).

Expected Result

The injected trace meta tags don't interfere with React hydration: no hydration errors, and the server-rendered DOM is preserved.

Actual Result

Every SSR page load logs:

Uncaught Error: Minified React error #418; visit https://react.dev/errors/418?args[]=HTML&args[]= ...

("Hydration failed because the server rendered HTML didn't match the client.")

React recovers by re-rendering the whole document on the client, so the page looks fine, but:

  • SSR benefits are lost on every page load (full client re-render), with possible flicker and lost pre-hydration input/focus.
  • The recovery re-render rebuilds <head> and wipes the injected sentry-trace/baggage meta tags, so the distributed-tracing linkage this feature exists to provide is lost as well.
  • The constant #418 noise masks real hydration bugs.

Additional Context

No response

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Metadata

Metadata

Assignees

Fields

No fields configured for issues without a type.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions