cd /news/developer-tools/the-114kb-span-attribute-that-hid-ou… · home topics developer-tools article
[ARTICLE · art-8368] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↓ negative

The 114KB Span Attribute That Hid Our LCP Data

Debugging investigation where a React Native app's iOS Largest Contentful Paint (LCP) transaction data was missing from Sentry's Trace Explorer, despite the transaction existing. The root cause was that the `lcpUrl` attribute contained a 114KB base64-encoded image data URI, which Sentry normalized by truncating, causing other essential searchable attributes like `metricInfo` and `pageTitle` to be dropped from the indexed span data. Unlike iOS, Android's Chromium WebView automatically truncated the same data URI to 100 characters, preventing the issue on that platform.

read5 min views19 publishedMay 22, 2026

We recently ran into a strange Sentry performance issue in a React Native app. The short version: Our LCP transaction existed in Sentry, but we could not query it by the attributes we attached to it. That sounded contradictory at first. If the transaction was there, why could not Trace Explorer find it? The answer turned out to be a single span attribute: lcpUrl = data:image/png;base64,... In one iOS event, that value was about 114KB before Sentry normalized it. We were measuring WebView performance inside a React Native app. For each WebView page load, we reported two custom Sentry transactions:

Online Customer Support (FCP)
Online Customer Support (LCP)

Both transactions used the same operation: ui.web_page_load And we attached searchable attributes such as: metricInfo = FCP | LCP pageTitle pageUrl host path durationMs In Sentry Transaction Summary, the LCP transaction was visible. We could open sampled events and see slow LCP data for the page. But in Trace Explorer, this query returned no iOS LCP rows: span.op:ui.web_page_load metricInfo:LCP os.name:iOS pageTitle:"Online Customer Support" FCP worked. Android worked. iOS LCP did not. That is the kind of bug that makes you distrust the dashboard before you distrust your own instrumentation. When we pulled the raw event from the Sentry API, the iOS LCP transaction existed. The transaction name was correct: Online Customer Support (LCP) But the trace data was not what we expected. The event still had fields like: durationMs host lcpElement lcpUrl But important fields we expected to query by were missing from the indexed span data: metricInfo pageTitle pageUrl path The suspicious field was lcpUrl . For that iOS LCP event, lcpUrl was not a normal network URL. It was a base64 data URL: data:image/png;base64,... Sentry showed the value as truncated, and the event metadata indicated the original value was much larger. In our case, the original attribute value was around 114KB. This was the detail that made the investigation click. In production, the same WebView LCP instrumentation behaved very differently across platforms: The iOS LCP transaction was real, but it carried an enormous lcpUrl value. Sentry normalized the oversized field, and the attributes we depended on for aggregation were not available in Explore. That explains the contradiction: metricInfo , pageTitle , or path .Android looked fine because its entry.url value was already short before we sent it to Sentry. We built a small WebView test page to isolate where the truncation happened. The test page generated a real 800x500 PNG, embedded it as a base64 data URI, and made it the LCP candidate. The full data URI was 1,601,014 characters. On Android, the results were: DOM img.src.length = 1,601,014 AndroidBridge.postLcpUrl(url) = 1,601,014 PerformanceObserver entry.url = 100 That told us the React Native bridge was not truncating the value. A normal JavaScript-to-Android bridge could receive the full 1.6MB string. The 100-character value came from PerformanceObserver / Chromium before our injected script sent anything to React Native. On iOS Safari WebView, entry.url kept the full data URI. That is closer to the literal resource URL, but in this case it was exactly what made the telemetry event dangerous. The platform chain looked like this: iOS Safari WebView

PerformanceObserver -> entry.url = full base64 URI
-> postMessage to React Native
-> Sentry span attribute
-> oversized field normalized

-> aggregation attributes missing in Explore Android Chromium WebView

PerformanceObserver -> entry.url = 100 characters
-> Sentry span attributes stay small
-> dashboard query works

We also found Chromium source paths that use a 100-character URL elision pattern for internal presentation/logging-style output. I would treat this as an implementation detail, not a Web API contract. It explained our production data, but it is not something application logic should depend on. The browser Largest Contentful Paint API can expose information about the element that became the LCP candidate. Depending on the content, the entry may include a URL-like value for the resource. In a WebView, the largest contentful element can be an image. If that image is embedded as a data URL, the value can become: data:image/png;base64,<a very long string> Our injected WebView script collected that value and forwarded it to React Native. React Native then attached it as a Sentry span attribute. That was the mistake. We did not actually need the image bytes. We only wanted to measure page performance by page title, host, path, metric type, and duration. We removed lcpUrl and lcpElement from the Sentry span attributes. Before, our LCP payload included extra diagnostic details:

reportWebSpan("LCP", lcpValue, {
lcpElement,
lcpUrl,
});

After the fix, we only report the metric value and stable dimensions: reportWebSpan("LCP", lcpValue); The Sentry attributes now stay small and useful: metricInfo pageTitle pageUrl host path durationMs After removing the oversized fields, the LCP data appeared correctly in the query again. This bug came from a well-intentioned bit of instrumentation code. It tried to capture more context for LCP debugging. That sounds reasonable when you are writing the code. But observability data has different rules from application data. A field is not harmless just because it is technically available. Before sending it to a telemetry system, it should pass a few checks: lcpUrl failed those checks. lcpElement was also not needed for our dashboard. It added noisy DOM details without improving the metrics we actually used. One more small trap: our performance transactions were sampled. With tracesSampleRate: 0.2 , historical examples were naturally sparse. That made the bug feel more mysterious than it was. For custom performance instrumentation, especially in WebViews, keep span attributes boring. Good attributes are small, stable, and useful for grouping: metricInfo = LCP pageTitle = Online Customer Support host = example.com path = /customer-service durationMs = 3020 Bad attributes are large, unique, or accidentally full of payload data:

lcpUrl = data:image/png;base64,...
outerHTML = <huge DOM subtree>

requestBody = ... My final notes from this one: entry.url is not equally useful across WebViews. Android Chromium may give you a short, elided value, while iOS Safari WebView may give you the full data URI.The dashboard was not wrong. The data model was wrong. And in this case, deleting two fields made the metric visible again.

── more in #developer-tools 4 stories · sorted by recency
── more on @sentry 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-114kb-span-attri…] indexed:0 read:5min 2026-05-22 ·