Why Safari Said 'Link Not Found' (And Chrome Didn't)

Published: (April 21, 2026 at 09:35 PM EDT)
4 min read
Source: Dev.to

Source: Dev.to

Problem

A URL shortener we built showed “Link Not Found” errors on Safari (iOS and macOS) while Chrome, Firefox, and Edge worked fine. Users tapping a short link on iPhone sometimes saw a brief flash of an error page before the destination loaded. The issue was intermittent, hard to reproduce, and never occurred on Chrome.

Investigation

We added lightweight debug instrumentation: a useRef array that logged every function call with timestamps and persisted to localStorage so the logs survived navigation.

Sample debug output from a user’s device:

[0] { event: "handleRedirect called", timestamp: 1709683200001 }
[1] { event: "API success, redirecting", timestamp: 1709683200250 }
[2] { event: "handleRedirect called", timestamp: 1709683200252 }
[3] { event: "API error: request cancelled", timestamp: 1709683200260 }

Entry [2] revealed that handleRedirect was being called twice, the second call occurring just 2 ms after the first successful redirect.

What the component did

  1. MountuseEffect called handleRedirect()
  2. handleRedirect() resolved the target URL via API
  3. On success, safeRedirect(targetUrl) set window.location.href
  4. After navigation, state was updated (markVisitedInSession) turning hasVisitedInSession from falsetrue
  5. handleRedirect was wrapped in useCallback with hasVisitedInSession as a dependency

When the state changed, React created a new handleRedirect callback identity. The useEffect detected a changed dependency and re‑ran the effect, invoking handleRedirect again—even though the page was already navigating away.

Root Cause: Safari’s navigation behavior

  • Chrome: Setting window.location.href immediately halts JavaScript execution on the current page. The second handleRedirect never runs.
  • Safari: JavaScript continues to run while navigation is in progress. The second call fires, starts an API request, which gets cancelled because the page is mid‑navigation. The catch block renders the error page, producing the flash of “Link Not Found”.

Fix

We introduced a guard using a useRef (instead of useState) to ensure the redirect logic runs only once.

// Guard to prevent double redirects
const hasRedirected = useRef(false);

const handleRedirect = useCallback(async (pwd?: string) => {
  // If we already redirected and this isn’t a password retry, bail out
  if (hasRedirected.current && !pwd) return;

  // …resolve URL, handle previews, etc…

  // Mark that we have redirected before navigating
  hasRedirected.current = true;
  safeRedirect(targetUrl, '/');
}, [/* other deps */]);

Why useRef works

  • No re‑render: Updating a ref does not trigger a component render, so the callback identity stays the same.
  • Persistence across renders: The ref value survives across renders, silently blocking subsequent calls.

If we had used useState for the guard, setting it to true would cause another render, potentially creating a new callback identity and re‑triggering the effect—a loop we wanted to avoid.

Aftermath

  • Verified the fix across Safari, Chrome, and Firefox.
  • Removed the debug instrumentation (localStorage logging and the debug panel).
  • Kept the useRef guard as a three‑line defense against this Safari‑specific behavior.

Takeaways

  • Browser navigation differs: Chrome halts JS on window.location.href; Safari does not. This isn’t a bug in either browser—specs don’t dictate when execution must stop.
  • React hooks can create hidden loops: useCallback + useEffect dependencies that include state updated inside the callback can cause invisible re‑executions, especially when navigation intervenes.
  • Instrument production when needed: When a bug can’t be reproduced locally, ship minimal instrumentation behind a flag to let real devices reveal the truth.
  • Prefer useRef for non‑rendering state: When you need mutable state that must not trigger renders (e.g., a “has redirected” flag), useRef is the appropriate tool.

Ever been bitten by a Safari‑specific JavaScript behavior? Share your story in the comments.

Built at jo4.io – a URL shortener that works on every browser, including Safari.

0 views
Back to Blog

Related posts

Read more »