Skip to content

feat(preview): capture sandbox preview iframe in PostHog session replay - #5203

Open
aka-sacci-ccr wants to merge 1 commit into
mainfrom
posthog-session-replay-iframes
Open

feat(preview): capture sandbox preview iframe in PostHog session replay#5203
aka-sacci-ccr wants to merge 1 commit into
mainfrom
posthog-session-replay-iframes

Conversation

@aka-sacci-ccr

@aka-sacci-ccr aka-sacci-ccr commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

PostHog session replay was showing the Preview canvas as a blank rectangle. The preview <iframe> loads a deco site on a different origin (*.preview-studio.decocms.com) than the app (studio.decocms.com), and rrweb only records same-origin iframes by default.

This enables PostHog's cross-origin iframe recording so the deco site being previewed is stitched into the parent's recording:

  • Parent (lib/posthog-client.ts): set session_recording.recordCrossOriginIframes: true.
  • Child (components/sandbox/preview/session-replay-script.ts, new): a lean posthog-js bootstrap injected into the preview iframe, inited with the same flag. The child detects it's embedded and forwards its rrweb stream to the parent via postMessage instead of ingesting on its own → one stitched recording.
  • Injection (preview.tsx): on iframe onLoad, after the display.mode === "sandbox" guard, via the sandbox's existing visual-editor::activate channel (new Function(script)() in IFRAME_BOOTSTRAP_SCRIPT).

Scoping — only Preview-tab iframes (deco sites)

The visual-editor::activate bootstrap is injected by the daemon proxy only into sandbox dev-server previews, never the production-fallback frame; combined with the display.mode === "sandbox" guard, recording is scoped to deco sites the user is actively editing. The child disables autocapture/pageviews/pageleaves and mirrors the parent's input masking (maskAllInputs, blockClass: "ph-no-capture"), so it contributes only the replay stream — no duplicate analytics events from inside the customer site.

Testing notes

Local dev already serves the preview cross-origin (<handle>.localhost), so the path is reproducible, but this PR is meant to be validated on the preview env:

  1. Open a VM Preview and interact with it.
  2. In the preview iframe's console: window.__phSessionReplayActive === true and window.posthog exists; Network shows /static/array.js.
  3. In the parent: posthog.config.session_recording.recordCrossOriginIframes === true; open posthog.get_session_replay_url().
  4. Confirm the replay renders the preview content instead of a blank rectangle.

Session replay is gated by project-level sampling (10%) + min duration (10s); the child inherits the parent's session, so no separate config.

⚠️ One thing to confirm on the deployed env

The child loads posthog-js from ${POSTHOG_HOST}/static/array.js (the same first-party reverse proxy the parent uses). That proxy must serve /static/* (standard PostHog reverse-proxy contract). If array.js 404s inside the iframe, the recorder won't load — please confirm on the preview env.

Checklist

  • bun run fmt / fmt:check
  • bun run check (tsc)
  • bun run lint

Summary by cubic

Enable cross-origin session replay for the Preview iframe so deco sites render inside PostHog recordings instead of a blank box. The parent enables cross-origin capture and the sandbox iframe injects a minimal posthog-js bootstrap to forward rrweb events to the parent.

  • New Features

    • Parent: set session_recording.recordCrossOriginIframes: true and keep input masking.
    • Child: new buildSessionReplayScript() in the preview iframe using posthog-js; disables autocapture/pageviews and mirrors masking; forwards rrweb to the parent via postMessage for one stitched recording.
    • Injection: on iframe load in preview.tsx via the existing visual-editor::activate channel, gated by display.mode === "sandbox" and project config.
    • Scope: runs only in sandbox dev-server previews, never the production fallback; no duplicate analytics from the customer site.
  • Migration

    • Ensure the PostHog host proxy serves /static/array.js (standard PostHog reverse-proxy path) so the iframe can load posthog-js.

Written for commit 28bd8fd. Summary will update on new commits.

Review in cubic

The Preview iframe loads a deco site on a different origin
(*.preview-studio.decocms.com), and rrweb only records same-origin
iframes — so the preview canvas showed up as a blank rectangle in
replays.

Enable PostHog cross-origin iframe recording:
- set `recordCrossOriginIframes: true` on the parent's session_recording
- inject a lean posthog-js bootstrap into the sandbox preview that inits
  with the same flag, so the child forwards its rrweb stream to the
  parent instead of ingesting on its own (one stitched recording)

The child bootstrap is injected only via the sandbox's existing
`visual-editor::activate` channel (present only in sandbox dev-server
previews, never the production fallback), and after the
`display.mode === "sandbox"` guard — so recording is scoped to deco
sites the user is actively editing in Preview. It disables
autocapture/pageviews and mirrors the parent's input masking, so it
contributes only the replay stream.
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.

1 participant