Fix Session Replay Not Recording iframes and Shadow DOM (2026)
EngineeringOctober 28, 202612 min read

Fix Session Replay Not Recording iframes and Shadow DOM (2026)

Blank rectangles in replay? Shadow DOM missing? Here's the fix.

Say you've spent four hours working out why users abandon checkout. The funnel says 23% drop off at the payment step. Session replays show them clicking the "Proceed to Payment" button, then... a blank gray rectangle. For thirty seconds. Then they leave.

The blank rectangle is a Stripe checkout iframe. Cross-origin. Invisible to the replay SDK. It looks like a loading spinner that isn't one: the replay tool is admitting defeat.

Session replay doesn't record everything. Cross-origin iframes and shadow DOM web components are the two most common blind spots, and they happen to contain some of your most important user interactions: payment forms, embedded scheduling widgets, third-party chat, custom design system components. The stuff where things actually break.

This guide covers why these blind spots exist, how to diagnose them, and—where possible—how to capture what's happening inside.

What You'll Have by the End

By the end of this tutorial, you'll be able to:

  • Diagnose why specific elements appear blank or missing in your replays
  • Test shadow DOM capture for web components in JustAnalytics
  • Check same-origin iframe recording for embeds you control
  • Understand why cross-origin iframes can't be recorded (and what metadata you can capture instead)
  • Debug the most common iframe and shadow DOM recording failures

The fixes are mostly testing and small code changes. But understanding why these limitations exist will save you from chasing impossible solutions—like trying to record Stripe iframe contents. (Spoiler: you can't. PCI compliance exists for reasons.)

Prerequisites

Before starting:

  • A JustAnalytics account with session replay enabled (a Pro feature, $49/month or $39/month billed annually)
  • Basic understanding of iframes, shadow DOM, and browser security policies
  • Access to deploy code changes to your app
  • Chrome DevTools or equivalent for debugging DOM structure

If you're new to session replay entirely, our GDPR-compliant session replay guide covers the privacy fundamentals. This post assumes you've got replay running and you're troubleshooting why certain things don't show up.

Why iframes and Shadow DOM Break Replay

Session replay works by observing DOM mutations. A MutationObserver watches for changes—elements added, removed, attributes modified—and serializes them into a recording. On playback, those mutations rebuild the page state frame by frame.

This works beautifully for the main document. It falls apart at two boundaries:

iframes create separate browsing contexts. A cross-origin iframe (like Stripe, YouTube, or a third-party form) runs JavaScript from a different origin. The browser's same-origin policy prevents your parent page's JavaScript—including your replay SDK—from accessing anything inside that iframe. Not the DOM, not the events, nothing. Security feature, not a bug.

Shadow DOM creates encapsulated DOM trees attached to elements. A standard MutationObserver on document.body doesn't automatically see mutations inside shadow roots. You need explicit handling to attach observers to shadow trees, and many replay tools either don't do this or make it opt-in.

Result: blank rectangles where iframes should be, invisible components where shadow DOM lives. Frustrating? Incredibly. Design systems built entirely on web components can make the replay look like a wireframe with holes in it—completely useless for debugging the actual user experience. Honestly, this is one of my biggest gripes with modern web architecture: we keep adding abstraction layers that break our observability tools.

Step 1: Diagnose What's Actually Missing

Open your app in Chrome DevTools. Right-click the element that's blank in replays and select "Inspect."

For iframes, check the src attribute:

<!-- Same-origin — you can potentially record this -->
<iframe src="/embed/scheduler"></iframe>

<!-- Cross-origin — recording the contents is impossible -->
<iframe src="https://js.stripe.com/v3/elements-inner-card"></iframe>

If the iframe's origin differs from your page's origin, your page's scripts cannot read its contents. Full stop. Browser security prevents it, and for a third-party frame there's no workaround that doesn't involve man-in-the-middling the iframe (which breaks functionality and violates terms of service).

For shadow DOM, look for the #shadow-root label in the Elements panel:

▶ my-custom-button
    #shadow-root (open)
        <button class="internal">Click me</button>

If you see #shadow-root, that element uses shadow DOM. If your replay shows nothing where this component renders, your replay tool isn't traversing shadow trees. That's your culprit.

Quick test: In your JustAnalytics dashboard, watch a replay and compare it to the live page. Make a list of what's missing:

  • Payment forms (usually cross-origin iframes)
  • Chat widgets (often cross-origin or shadow DOM)
  • Custom UI components (shadow DOM if you're using web components)
  • Embedded calendars, maps, videos (cross-origin)

This list tells you which category each problem falls into.

Step 2: Test Shadow DOM Capture

Shadow DOM capture requires a replay recorder to attach a separate MutationObserver to every shadow root it finds. Whether the JustAnalytics recorder does this for your page is something to test, not assume: check the session replay docs for your plan, then confirm against your own pages.

The test takes five minutes. Render one open-mode web component with a visible label, load the page with replay on, and watch the recording. Label there? The recorder reached into the shadow tree. Empty box? It didn't, and no config flag I can point you to will change that.

Nested components are the usual trouble spot. A modal that wraps a button that wraps an icon is three shadow roots deep, and a recorder that stops early gives you a modal frame with nothing inside. Test your deepest component, not your simplest one. Bigger shadow trees also mean bigger recordings, so keep an eye on payload size once you've confirmed capture works.

For LogRocket and FullStory users migrating: both treat shadow DOM as a setting to check, and the option names differ between vendors. Read their current docs rather than copying a flag from an old forum thread.

Step 3: Record Same-Origin iframes

If you control both the parent page and the iframe content, meaning they share the same origin, the iframe's document is reachable from the parent, so recording it is possible in principle. Whether the JustAnalytics recorder follows into the frame is another thing to verify on your own page rather than take from a blog post.

The practical checklist:

  • Confirm the origins match exactly: scheme, host and port. app.example.com and embed.example.com are same-site but not same-origin, so the reachability rule fails for them.
  • Load the tracker script tag in the iframe's own <head> as well as the parent, and make it load early. Early mutations are lost if the script arrives after the frame has rendered.
  • Watch a replay of a real session and compare it to the live page. Blank frame means the recorder didn't get in.

Subdomain setups are where this gets ugly. You can fight it with document.domain tricks (deprecated, and browsers are removing them) or accept the limitation and track events around the frame instead.

Look, I'll be honest: same-origin iframe recording sounds simple in theory but it's fiddly in practice. Budget extra time for debugging.

Step 4: Handle Cross-Origin iframes (What You Can't Record)

Here's the uncomfortable truth: the contents of a third-party cross-origin iframe, one you can't add your own script to, cannot be recorded. Period. Not by JustAnalytics, not by LogRocket, not by FullStory, not by anyone. The browser prohibits it.

What you can capture:

  • The iframe element itself — dimensions, position, when it appeared in the DOM
  • Parent page interactions — what the user did before and after interacting with the iframe
  • Load timing — when the iframe's load event fired
  • Errors in your code — JavaScript exceptions in your checkout flow handling

Here's how to maximize useful data around cross-origin iframes:

// Track when users enter the checkout iframe flow
const stripeIframe = document.querySelector('iframe[src*="stripe"]');

stripeIframe?.addEventListener('load', () => {
  window.JustAnalytics.track('checkout_iframe_loaded', {
    ms_since_page_start: Math.round(performance.now()),
    iframe_host: new URL(stripeIframe.src).hostname,
  });
});

// Track interactions immediately before and after
document.querySelector('.checkout-button')?.addEventListener('click', () => {
  window.JustAnalytics.track('checkout_initiated');
});

When you correlate these events with replays, you can see: user clicked checkout → iframe loaded → user went idle for 45 seconds → user left. You can't see what happened inside the iframe, but you know the iframe loaded successfully and the user didn't complete the action. That narrows down the debugging.

Step 5: Debug Common Recording Failures

Shadow DOM elements show as empty in replay

Run the five-minute test from Step 2 with an open-mode component first. If you're using closed shadow roots (mode: 'closed'), JavaScript from outside the component can't access them—including replay SDKs. Consider switching to open shadow roots if replay visibility matters more than encapsulation.

Same-origin iframe shows blank even with the child script installed

The child script must load before the iframe finishes rendering, or early mutations get missed. Move the script tag higher in the iframe's <head>. Also check for CSP headers blocking the script—look for Content-Security-Policy violations in the console.

Recording shows the iframe, but interactions inside look glitchy

You might be seeing partial capture. The parent SDK recorded the iframe element, but the child SDK either didn't load or crashed mid-session. Check for JavaScript errors in the iframe's context. Try running the iframe in isolation to see if its replay works independently.

Web components from a third-party library don't record

Some component libraries use closed shadow roots or don't expose their DOM structure. Check the library's source—if it's using attachShadow({ mode: 'closed' }), you can't observe inside without modifying the library. Fork it or file a feature request.

My Shopify checkout isn't recording

Shopify hosts and controls its checkout, so your replay script can't observe it. Same outcome as Stripe: you can track when users enter checkout and when they return, but not what happens in Shopify's flow.

What You Can't Fix (And Why That's Okay)

Some limitations exist for good reasons:

Cross-origin isolation protects users. If your replay could see inside a Stripe iframe, so could malicious scripts. Credit card numbers would leak into third-party databases. The browser's same-origin policy exists because the alternative is catastrophic.

Closed shadow roots indicate intentional encapsulation. The component author decided internals shouldn't be exposed. Sometimes this is overzealous (a button doesn't need closed shadow DOM), sometimes it's appropriate (password managers, security-sensitive widgets).

Third-party embed content isn't yours to record. YouTube's video player, Intercom's chat widget, Calendly's scheduler—these are products with their own users and their own privacy expectations.

What matters for debugging is usually visible anyway: the user's actions before and after the blind spot, timing of events, errors in your code. If users are abandoning your Stripe checkout, the issue is almost always something that happened on your page—a confusing CTA, missing error handling, or a loading state that looked like a failure.

Is it annoying that we can't just see everything? Yeah. Absolutely. But the trade-off makes sense. The alternative—where any script can spy on any iframe—would be a security nightmare.

Next Steps

Now that you understand where replay blind spots come from:

  • Audit your site for shadow DOM components—especially if you're using a modern design system like Lit, Stencil, or native web components
  • Check your iframes' origins and decide which ones (if any) you can capture with the same-origin approach
  • Set up event tracking around cross-origin iframes to capture useful metadata even when you can't see inside
  • For error correlation during checkout flows, our error tracking and funnel analysis guide covers linking JavaScript exceptions to specific replay sessions

If you're building a new app with web components, consider making shadow DOM capture part of your development workflow from day one—testing that components render in replay saves debugging time later. If your architecture spans several of your own services, JustAnalytics' distributed tracing follows a request across them, which session replay doesn't show.

The goal isn't to record everything. It's to record enough to understand what went wrong. Perfect visibility is a myth anyway—you're always working with incomplete information. Get comfortable with that.

Frequently Asked Questions

Why does session replay show blank rectangles instead of iframes?

Cross-origin iframes block the parent page's JavaScript from accessing their DOM due to browser security policies. Your replay SDK runs on the parent page, but it can't see inside a cross-origin iframe—so it renders as an empty rectangle. Same-origin iframes can be captured if you inject the replay script into the iframe document, but for third-party content (Stripe, YouTube, embedded forms) you accept the limitation and track events around the frame instead.

Can session replay record shadow DOM web components?

Yes, but only if your replay tool explicitly supports it. Shadow DOM creates an encapsulated DOM tree that standard MutationObserver doesn't traverse automatically. A recorder has to attach observers to each shadow root it finds, so test your own components in a replay to confirm. Older tools or misconfigured setups miss shadow trees entirely, leaving your custom elements invisible in playback.

How do I capture Stripe checkout in session replay?

You can't capture the actual Stripe iframe content—Stripe serves it from a different origin and blocks cross-origin access for security (PCI compliance requires this isolation). What you can capture: the container element dimensions, when the iframe loads, and user interactions on your page before and after checkout. That's usually enough to debug checkout flow issues without seeing card details.

Does shadow DOM capture affect performance?

Only a little. Attaching MutationObservers to shadow roots adds some overhead, but the bigger concern is recording size—if you have hundreds of web components with deep shadow trees, your replay payloads grow. Test your deepest components and watch payload size.


Try JustAnalytics

All-in-one observability in one under-5KB script: cookieless analytics + error tracking + APM + session replay + uptime + structured logs. Replaces GA4 + Sentry + Datadog + Pingdom + LogRocket. Free tier (100K events/mo), Pro $49/month ($39/month billed annually).

Start free → · AI Command Center MCP

JP
JustAnalytics Platform TeamContributor

Author at JustAnalytics.

Related posts