Stop Saying 'Can't Reproduce': Tracing an Error Back to the Session That Caused It
The error happened. You can't reproduce it. But the session replay shows exactly what the user did—and it's one click away from the stack trace.
The ticket came in at 4:47pm on a Friday.
"Users are reporting they can't complete checkout. Error message says something about payment. Can't reproduce on my machine."
I'd read that exact sentence maybe two hundred times in my career. "Can't reproduce." Three syllables that turn a 5-minute fix into a 3-day detective hunt. Three syllables that make you question whether users are making things up. (They're not. They never are.)
I pulled up Sentry. Found the error: TypeError: Cannot read properties of undefined (reading 'cardNumber'). 147 occurrences in the last hour. Stack trace pointed to line 234 of PaymentForm.tsx. I opened the file. Line 234 was a perfectly normal call to our form state. Nothing obviously wrong.
Checked the browser breakdown. Safari 17.4 on macOS. Firefox 125 on Windows. Chrome 124 on Android. No pattern. Checked the URL. All on /checkout/payment. Checked the user accounts. Mix of new and returning. Checked the release. Current production, no recent deploys.
Forty minutes in, I still had nothing. The error was real—147 users don't hallucinate the same TypeError—but I couldn't make it happen. Clicked through the checkout flow a dozen times. Perfect every time. Tried Safari, Firefox, Chrome, mobile emulators. Nothing.
I hate this part. The part where you start doubting yourself. Maybe I'm testing wrong. Maybe there's a deploy I missed. Maybe I should just go home and pretend I didn't see the ticket.
And then I remembered: we'd added session replay three weeks ago. On the same SDK. Same session ID.
One click on the error event. "View Session Replay." The recording jumped to 14:32:47—exactly when the error fired.
I watched a user fill out the shipping form. Normal. They clicked "Continue to Payment." The payment form loaded. They started typing their card number. And then—they tabbed away to check something. Came back. The form was blank. They clicked the input field. Nothing. They clicked "Pay Now" without re-entering the card.
TypeError: Cannot read properties of undefined (reading 'cardNumber').
The bug: our payment SDK lost focus state when the browser tab went inactive for more than 30 seconds. Some kind of timeout or garbage collection on the SDK's end. When the user tabbed back, the form inputs were there visually but the SDK had dropped the binding. The form data existed in React state but not in the payment SDK's internal state.
Five minutes from "can't reproduce" to root cause. Because I could literally watch what the user did.
Why Separate Error Tracking and Session Replay Don't Cut It
The obvious solution: use Sentry for errors, LogRocket (or FullStory, or Datadog Session Replay) for recordings. Two tools, one problem solved. Right?
Not quite.
The integration tax. To connect Sentry and LogRocket, you need to configure both SDKs to share session context. LogRocket's Sentry integration requires you to pass LogRocket's session URL into Sentry's extra context, then enable the integration in LogRocket's dashboard. It works—eventually—but it's another thing to configure, another thing to break, another place to check when debugging your debugging tools. I've spent entire mornings debugging integrations instead of debugging my actual product. Not a great use of anyone's time.
The cost stacking. Sentry's Team plan is $26/month for 50K errors. LogRocket starts at $99/month for 10K sessions. Already you're at $125/month minimum, and those session counts are low for a production app. Scale up and you're looking at $300-500/month easily—just for the privilege of correlating two data sources that should have been unified from the start.
The timestamp dance. Even with integration enabled, you're correlating by approximate timestamp. Error at 14:32:47 maps to a replay that started at 14:31:02 and ended at 14:35:18. You scrub through 4+ minutes of footage looking for the moment. It's better than nothing. It's worse than jumping directly to the exact second.
The context gap. Sentry knows about the error. LogRocket knows about the user's actions. Neither knows about your analytics funnel, your custom events, your business context. If you want to correlate "this error caused a funnel drop-off" or "users who hit this error had 3x higher bounce rates," you're exporting to a data warehouse and writing SQL.
I've done this workflow. Plenty of times. It's not terrible. But every time I caught myself thinking: why am I paying two companies for data they refuse to share cleanly?
Look, I'm not saying Sentry is bad. It's not. It's excellent at what it does. Same with LogRocket. But gluing them together feels like duct-taping a solution that should just... exist.
The One-Click Fix: Unified Error + Replay
Here's what the workflow looks like when error tracking and session replay share the same session ID, the same event timeline, the same database.
Error appears in dashboard. You click it. You see:
- Stack trace (source-mapped, of course)
- Browser, OS, device
- User's session ID
- Custom properties (user tier, feature flags, whatever you track)
- "View Session Replay" button
You click the button. The replay starts at the exact moment the error fired. Not 30 seconds before. Not "somewhere in this 4-minute session." The exact frame.
You watch. You see what the user did. You understand why.
That's it. No integration configuration. No timestamp matching. No separate dashboards. The error and the replay are the same session, captured by the same SDK, stored in the same place.
For the payment SDK bug I mentioned: I watched the replay, saw the tab-away behavior, reproduced it locally in under a minute, and had a fix pushed within the hour. Friday afternoon crisis averted. (The fix was to re-initialize the payment SDK binding on visibility change. Took 12 lines. Embarrassingly simple once you know the cause.)
What This Looks Like in Practice
Here's the JustAnalytics SDK initialization for a typical React app:
import { JA } from '@justanalytics/browser';
JA.init({
siteId: 'your-site-id',
errorTracking: true,
sessionReplay: true,
// Privacy defaults: masks all inputs, text, images
// Override with replayMaskSelectors and replayUnmaskSelectors
});
That's it. Errors are captured automatically (unhandled exceptions, promise rejections, console.error calls). Session replay records DOM mutations. Both share the same session context.
When an error fires, it's tagged with:
- Current page URL
- Session ID
- Replay timestamp (millisecond precision)
- Whatever custom properties you've attached (
JA.identify({ userId, plan, ... }))
In the dashboard, the error view shows a timeline. You see the error event. You see the replay segment. You click between them. The replay scrubber jumps to the error timestamp. You don't search for it. You don't guess. You just watch.
And because this is the same SDK that handles analytics funnels, you can answer questions like:
- "Did users who hit this error complete checkout?"
- "What percentage of checkout errors have replay footage?" (Hint: if you have 10% replay sampling, that's your answer)
- "What did users do immediately before hitting this error?"
Try answering those with Sentry + LogRocket + GA4. You'll need BigQuery and a free afternoon.
The Bugs This Catches
Over the past month, unified error-to-replay linking surfaced these actual bugs (anonymized from real sessions):
The clipboard paste failure. User copied a discount code from email, pasted it into the promo code field. The paste event fired but the field showed empty. Error: ValidationError: Promo code cannot be empty. The replay showed the user paste, see nothing appear, try again, give up. Root cause: a CSS user-select: none on the parent container was preventing paste on Safari. No error in the traditional sense—just a silent UX failure that looked like user error.
The double-click disaster. User double-clicked the "Place Order" button. First click initiated the request. Second click fired before the button disabled. Both requests hit the server. Server processed both. User got charged twice. Error didn't fire in the browser—the "error" was a Slack message from support. But session replay showed exactly what happened, down to the 47ms between clicks. Fix: debounce and instant disable-on-click.
The race condition nobody believed. Redux state update fired. Component re-rendered. A child component tried to access state that hadn't propagated yet. undefined error. QA couldn't reproduce because their machines were faster. Replay showed a 200ms delay between action dispatch and state availability—enough for the component to render with stale data. The fix was a proper loading state. Classic stuff, but the replay made it undeniable.
None of these were exotic. All of them were real users, real money lost, real support burden. And all of them were "can't reproduce" until someone watched the footage.
I'll be honest: I felt kind of dumb after the double-click one. We should've had debounce on that button from day one. But hindsight's cheap. The point is we found it.
The GDPR Question
"Session replay sounds like a privacy nightmare." Fair concern.
JustAnalytics masks sensitive content by default:
- All
<input>fields show*****instead of actual keystrokes - Text content is replaced with blocks unless you explicitly whitelist
- Images can be masked or placeholder'd
- Selectors you specify are masked entirely (for PII-heavy areas)
You can tune this. Some teams unmask non-sensitive inputs (like search bars) for better debugging context. Some teams mask entire sections (like account settings). The default is conservative: assume everything is sensitive, prove otherwise.
For GDPR-compliant replay configuration, the key is documenting your masking strategy and ensuring nothing identifiable leaks through. We've written a full walkthrough on that.
And because the replay data lives in the same system as your analytics—not exported to a third-party vendor—you maintain data residency control. For EU teams worried about Schrems III implications, that matters.
Sentry + LogRocket vs. Unified Platform: Honest Comparison
Let's be fair. Sentry is excellent error tracking software. LogRocket is a solid session replay tool. If you're already paying for both and have the integration working, you might not need to switch.
But here's the actual comparison:
| Factor | Sentry + LogRocket | JustAnalytics |
|---|---|---|
| Error-to-replay linking | Via integration, approximate timestamp | Native, exact timestamp, one click |
| Setup | Two SDKs, integration config, context passing | One SDK, enabled by default |
| Monthly cost (50K errors, 10K sessions) | ~$125+ combined | $49 (Pro plan, includes 1M events) |
| Shared session context | Requires configuration | Automatic |
| Analytics correlation | Requires third tool (GA4, Mixpanel, etc.) | Built-in, same session ID |
| SDK payload | Two scripts, larger total | One script, under 5KB |
The JustAnalytics approach isn't strictly better for every use case. If you need Sentry's deep integrations with 50+ frameworks, or LogRocket's enterprise features like co-browsing, those are real differentiators. This is about whether you need those features—or whether you mostly need "error happens, show me what user did."
For most teams, that's the core use case. And that's what unified does better.
(Full disclosure: I work on JustAnalytics. But I also spent years in the Sentry+LogRocket world before this. I'm not making up the frustrations.)
The Larger Point
"Cannot reproduce" is usually "cannot imagine what the user did." We assume users follow happy paths. They don't. They tab away. They double-click. They have weird extensions. They copy-paste from weird sources. They use browsers we don't test on machines we don't own.
Users are creative. Chaotically so.
Session replay is a window into reality. Error tracking tells you what broke. Replay shows you why it broke. And when those two pieces live in the same system—same session, same timestamp, one click away—debugging stops being guesswork.
The payment SDK bug from the opening? "Can't reproduce" to "fixed in production" in under an hour. That's the difference. Not because I'm a better debugger. Because I could see what the user saw.
For teams running paid acquisition through ClickzProtect, this correlation becomes critical—you need to know if errors are causing legitimate users to bounce. For call campaigns with VeloCalls, correlating web errors with call abandonment tells you whether landing pages are the problem. And for browser environment testing with JustBrowser, replay footage across browser profiles catches compatibility bugs before users do.
We've written more about why unified observability beats the five-tool stack and how to correlate errors with funnel drop-off. For framework-specific setup, see our Django middleware tutorial or Next.js 15 guide.
Frequently Asked Questions
How does JustAnalytics link errors to session replays automatically?
Both error tracking and session replay share the same session ID and event timeline. When an exception fires, we capture the exact timestamp and session context. Clicking the error in the dashboard jumps you to that moment in the replay—no manual correlation, no timestamp matching, no separate tools.
Can I get this error-to-replay linking with Sentry and LogRocket?
Yes, but it requires their paid integration ($29/month add-on for LogRocket, plus configuration). You need to pass Sentry's session ID to LogRocket and enable the integration on both sides. With JustAnalytics, it's automatic because both features run on the same SDK and share context natively.
Does session replay capture sensitive user data like passwords?
Not by default. JustAnalytics masks all input fields, text content, and sensitive elements automatically. You can configure additional selectors to mask, or whitelist specific elements you want to capture. The default is privacy-safe for GDPR and CCPA compliance.
How much does session replay add to page load time?
The full JustAnalytics SDK—including analytics, error tracking, and session replay—is under 5KB gzipped. Session replay records DOM mutations asynchronously and doesn't block rendering. Most sites see no measurable impact on Core Web Vitals.
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 annual).
Author at JustAnalytics.