{"slug": "the-114kb-span-attribute-that-hid-our-lcp-data", "title": "The 114KB Span Attribute That Hid Our LCP Data", "summary": "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.", "body_md": "We recently ran into a strange Sentry performance issue in a React Native app.\nThe short version:\nOur LCP transaction existed in Sentry, but we could not query it by the attributes we attached to it.\nThat sounded contradictory at first. If the transaction was there, why could not Trace Explorer find it?\nThe answer turned out to be a single span attribute:\nlcpUrl = data:image/png;base64,...\nIn one iOS event, that value was about 114KB before Sentry normalized it.\nWe were measuring WebView performance inside a React Native app. For each WebView page load, we reported two custom Sentry transactions:\nOnline Customer Support (FCP)\nOnline Customer Support (LCP)\nBoth transactions used the same operation:\nui.web_page_load\nAnd we attached searchable attributes such as:\nmetricInfo = FCP | LCP\npageTitle\npageUrl\nhost\npath\ndurationMs\nIn Sentry Transaction Summary, the LCP transaction was visible. We could open sampled events and see slow LCP data for the page.\nBut in Trace Explorer, this query returned no iOS LCP rows:\nspan.op:ui.web_page_load metricInfo:LCP os.name:iOS pageTitle:\"Online Customer Support\"\nFCP worked. Android worked. iOS LCP did not.\nThat is the kind of bug that makes you distrust the dashboard before you distrust your own instrumentation.\nWhen we pulled the raw event from the Sentry API, the iOS LCP transaction existed. The transaction name was correct:\nOnline Customer Support (LCP)\nBut the trace data was not what we expected. The event still had fields like:\ndurationMs\nhost\nlcpElement\nlcpUrl\nBut important fields we expected to query by were missing from the indexed span data:\nmetricInfo\npageTitle\npageUrl\npath\nThe suspicious field was lcpUrl\n.\nFor that iOS LCP event, lcpUrl\nwas not a normal network URL. It was a base64 data URL:\ndata:image/png;base64,...\nSentry 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.\nThis was the detail that made the investigation click.\nIn production, the same WebView LCP instrumentation behaved very differently across platforms:\nThe iOS LCP transaction was real, but it carried an enormous lcpUrl\nvalue. Sentry normalized the oversized field, and the attributes we depended on for aggregation were not available in Explore.\nThat explains the contradiction:\nmetricInfo\n, pageTitle\n, or path\n.Android looked fine because its entry.url\nvalue was already short before we sent it to Sentry.\nWe built a small WebView test page to isolate where the truncation happened.\nThe 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.\nOn Android, the results were:\nDOM img.src.length = 1,601,014\nAndroidBridge.postLcpUrl(url) = 1,601,014\nPerformanceObserver entry.url = 100\nThat told us the React Native bridge was not truncating the value. A normal JavaScript-to-Android bridge could receive the full 1.6MB string.\nThe 100-character value came from PerformanceObserver\n/ Chromium before our injected script sent anything to React Native.\nOn iOS Safari WebView, entry.url\nkept 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.\nThe platform chain looked like this:\niOS Safari WebView\nPerformanceObserver -> entry.url = full base64 URI\n-> postMessage to React Native\n-> Sentry span attribute\n-> oversized field normalized\n-> aggregation attributes missing in Explore\nAndroid Chromium WebView\nPerformanceObserver -> entry.url = 100 characters\n-> Sentry span attributes stay small\n-> dashboard query works\nWe 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.\nThe 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.\nIn a WebView, the largest contentful element can be an image. If that image is embedded as a data URL, the value can become:\ndata:image/png;base64,<a very long string>\nOur injected WebView script collected that value and forwarded it to React Native. React Native then attached it as a Sentry span attribute.\nThat was the mistake.\nWe did not actually need the image bytes. We only wanted to measure page performance by page title, host, path, metric type, and duration.\nWe removed lcpUrl\nand lcpElement\nfrom the Sentry span attributes.\nBefore, our LCP payload included extra diagnostic details:\nreportWebSpan(\"LCP\", lcpValue, {\nlcpElement,\nlcpUrl,\n});\nAfter the fix, we only report the metric value and stable dimensions:\nreportWebSpan(\"LCP\", lcpValue);\nThe Sentry attributes now stay small and useful:\nmetricInfo\npageTitle\npageUrl\nhost\npath\ndurationMs\nAfter removing the oversized fields, the LCP data appeared correctly in the query again.\nThis 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.\nBut observability data has different rules from application data.\nA field is not harmless just because it is technically available. Before sending it to a telemetry system, it should pass a few checks:\nlcpUrl\nfailed those checks.\nlcpElement\nwas also not needed for our dashboard. It added noisy DOM details without improving the metrics we actually used.\nOne more small trap: our performance transactions were sampled. With tracesSampleRate: 0.2\n, historical examples were naturally sparse. That made the bug feel more mysterious than it was.\nFor custom performance instrumentation, especially in WebViews, keep span attributes boring.\nGood attributes are small, stable, and useful for grouping:\nmetricInfo = LCP\npageTitle = Online Customer Support\nhost = example.com\npath = /customer-service\ndurationMs = 3020\nBad attributes are large, unique, or accidentally full of payload data:\nlcpUrl = data:image/png;base64,...\nouterHTML = <huge DOM subtree>\nrequestBody = ...\nMy final notes from this one:\nentry.url\nis 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.\nAnd in this case, deleting two fields made the metric visible again.", "url": "https://wpnews.pro/news/the-114kb-span-attribute-that-hid-our-lcp-data", "canonical_source": "https://dev.to/frankcode/the-114kb-span-attribute-that-hid-our-lcp-data-3291", "published_at": "2026-05-22 10:21:26+00:00", "updated_at": "2026-05-22 10:32:19.803964+00:00", "lang": "en", "topics": ["developer-tools", "data"], "entities": ["Sentry", "React Native", "iOS", "Android", "Trace Explorer", "WebView"], "alternates": {"html": "https://wpnews.pro/news/the-114kb-span-attribute-that-hid-our-lcp-data", "markdown": "https://wpnews.pro/news/the-114kb-span-attribute-that-hid-our-lcp-data.md", "text": "https://wpnews.pro/news/the-114kb-span-attribute-that-hid-our-lcp-data.txt", "jsonld": "https://wpnews.pro/news/the-114kb-span-attribute-that-hid-our-lcp-data.jsonld"}}