Errors on One Browser, Device or Country: Find and Fix

Some bugs only hit Safari, old Android phones, one country or people with an ad blocker. Learn how to spot a segment that fails far more than the rest of your site, reproduce it on real devices, and fix the usual causes in code.

To find an error that only happens on one browser, device or country, compare each segment's error rate with the whole site's rate and flag segments that fail at least twice as often, but only once they have enough visits to mean something. Then reproduce the bug on a real device or the real engine (not just a resized desktop window), and fix the usual causes: date and number parsing, missing browser APIs, storage that throws, blocked third-party scripts, and stale or regional assets.

Your own setup never shows these bugs: you build on desktop Chrome, tests run in Chromium, and the error total looks small while one group of visitors fails every time.

Step 1: Record the Segment With Every Visit and Error

You cannot compare segments you never stored. For each visit, keep the browser, operating system, device type (mobile, tablet, desktop) and country. Parse the user agent on the server, in one place, rather than trusting values sent from the client:

// One record per visit; errors reference the visit id.
type Visit = {
  id: string
  browser: string   // "Safari 16", "Chrome 129"
  os: string        // "iOS 16.6", "Android 9"
  device: 'mobile' | 'tablet' | 'desktop'
  country: string   // from the IP at ingest, then store a shortened IP
  hadError: boolean
}

Leave bots out: crawlers and headless browsers fail in odd ways and distort a segment.

Step 2: Compare Error Rate per Segment With the Site

The question is not "which browser has the most errors" (that is always your most popular browser). It is "which segment fails far more often than the site as a whole". Divide visits with an error by visits, per segment, and compare with the site rate. Require a minimum number of affected visits so one unlucky visit does not raise an alarm:

function problemSegments(visits: Visit[], key: keyof Visit, minErrorVisits = 5, ratio = 2) {
  const site = visits.filter(v => v.hadError).length / visits.length
  const groups = new Map<string, { visits: number; errors: number }>()

  for (const v of visits) {
    const g = groups.get(String(v[key])) ?? { visits: 0, errors: 0 }
    g.visits++
    if (v.hadError) g.errors++
    groups.set(String(v[key]), g)
  }

  return [...groups]
    .map(([segment, g]) => ({ segment, rate: g.errors / g.visits, ...g }))
    .filter(s => s.errors >= minErrorVisits && site > 0 && s.rate >= ratio * site)
    .sort((a, b) => b.rate / site - a.rate / site)
}

problemSegments(lastWeek, 'browser') // e.g. [{ segment: 'Safari 16', rate: 0.09, ... }]

Run it for browser, OS, device, country and page. A small segment failing far above the site rate is your bug, even with a few percent of traffic.

Do the same for speed: a segment much slower than the site points to a heavy asset, a slow region or a weak device class.

Step 3: Reproduce on the Real Engine

Device emulation in desktop DevTools changes the screen size, the user agent string and touch events. It does not change the engine: an "iPhone" in Chrome DevTools still runs Chrome's Blink engine, not Safari's WebKit. A Safari-only bug will not appear there.

Options that do reproduce it:

  • Real device clouds (BrowserStack-style services): real iPhones and Android phones with real browser versions. Match the exact OS version from your segment.
  • Playwright's WebKit build: close to Safari and cheap to run in CI, but not identical to the Safari a customer has.
  • Locale and time zone: many "country" bugs are really locale bugs. Playwright can set both:
const context = await browser.newContext({
  locale: 'de-DE',
  timezoneId: 'Europe/Berlin',
})
  • A VPN or a server in the region for CDN and geo-routing issues.
  • An ad blocker installed in your test browser.

Still stuck? See why production bugs don't reproduce locally.

Step 4: Fix the Common Root Causes

Date parsing (Safari). new Date("2026-10-22 10:00") is not a standard format. Chrome accepts it; older Safari returns Invalid Date, and everything after it breaks. Use ISO 8601 with a T, or parse the parts yourself:

// Bad: engine-dependent
const d = new Date(row.created_at) // "2026-10-22 10:00"

// Good: ISO 8601 with a time zone
const d = new Date(row.created_at.replace(' ', 'T') + 'Z')

Locale-dependent numbers. A German visitor types 1,5, and parseFloat("1,5") returns 1. Format with Intl.NumberFormat and normalise input before parsing, and never build logic on the output of toLocaleString().

Missing APIs on older browsers. Array.prototype.at, structuredClone or newer syntax throw TypeError on old Android WebViews and older iOS. Set a real build target and polyfill what you use:

// package.json
"browserslist": ["> 0.5%", "last 2 versions", "iOS >= 15", "Android >= 9", "not dead"]

Storage that throws. Some private modes and locked-down browsers throw when you touch localStorage. Wrap it:

function safeGet(key: string) {
  try { return localStorage.getItem(key) } catch { return null }
}

Ad blockers. A blocked analytics or chat script means window.gtag or a widget object is undefined, and code that calls it crashes. Guard every third-party global (window.gtag?.('event', …)) and never put critical logic behind a third-party script's load event. This is a classic source of Cannot read properties of undefined.

iOS layout and input quirks. 100vh includes the area behind Safari's toolbar, so a fixed "Pay" button can sit off screen. Use 100dvh with a 100vh fallback. Check <input type="date"> handling on iOS too.

CDN or region. If one country fails after a deploy, an edge location may serve old HTML that points to deleted chunks. See ChunkLoadError after deploy: keep old assets for a while and set cache headers so HTML is not cached long.

Step 5: Confirm the Fix per Segment

Rerun the same comparison after the fix. The segment's error rate should fall back to the site's; if only the overall rate drops, you fixed something else.

How Relia Shows Problems by Segment

Relia does this comparison for you from its session SDK. The Visitors page shows visitors, visits, page views, bounce, time per visit and visits that hit an error or struggled, broken down by page, source, country, device, browser and OS, with bots left out. Every row is a filter.

Its Where visitors hit problems list names segments whose visits hit an error at least twice as often as the site, or struggle at least twice as often, or whose LCP at the 75th percentile is 2.5 s or more and at least 1.5× the site's. On a demo shop with 4,928 visitors in 7 days (2,393 on mobile, 2,328 on desktop), it showed lines like "Visits on /checkout hit an error 4.7× as often as the site (1.2 % vs 0.2 %)" and "Visitors on /orders struggle 28.7× as often (19 % vs 0.7 % of visits)".

When an incident's affected visitors sit mostly in one browser, device or country, the incident says so ("Mostly: …"), as part of the explanation on paid plans. Relia never changes your code; you make the fix and watch the segment recover.

FAQ

How many visits does a segment need before I trust its error rate?

Count affected visits, not total visits. Five or more visits with an error in the segment is a reasonable floor; below that, a single unlucky user can make a tiny segment look broken.

Is Chrome DevTools device mode enough to test iPhone bugs?

No. It changes screen size and user agent but still runs Chrome's engine. Use a real iPhone, a device cloud, or at least Playwright's WebKit build.

Why does a bug only appear in one country?

Usually locale (date and number formats, translations that overflow a button), a CDN edge serving stale files, or a regional block on a third-party service. Test with that locale and time zone first, then from that region.

All guides