Safari가 “Link Not Found”라고 표시한 이유 (Chrome은 그렇지 않음)

발행: (2026년 4월 22일 AM 10:35 GMT+9)
7 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I need the actual text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line, formatting, markdown, and any code blocks unchanged while translating the rest into Korean.

Problem

우리가 만든 URL 단축 서비스가 Safari(iOS 및 macOS)에서는 “Link Not Found” 오류를 표시했지만 Chrome, Firefox, Edge에서는 정상적으로 작동했습니다. iPhone에서 짧은 링크를 탭하면 때때로 목적지 페이지가 로드되기 전에 오류 페이지가 잠깐 나타나는 현상이 있었습니다. 이 문제는 간헐적이며 재현하기 어려웠고 Chrome에서는 전혀 발생하지 않았습니다.

Investigation

우리는 가벼운 디버그 계측을 추가했습니다: useRef 배열을 사용해 모든 함수 호출을 타임스탬프와 함께 기록하고 localStorage에 저장해 네비게이션 후에도 로그가 남도록 했습니다.

사용자 기기에서 얻은 샘플 디버그 출력:

[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 }

엔트리 **[2]**는 handleRedirect두 번 호출되었음을 보여줍니다. 두 번째 호출은 첫 번째 성공적인 리다이렉트 후 2 ms 뒤에 발생했습니다.

What the component did

  1. MountuseEffecthandleRedirect()를 호출
  2. handleRedirect()가 API를 통해 대상 URL을 해결
  3. 성공 시, safeRedirect(targetUrl)window.location.href를 설정
  4. 네비게이션 후, 상태가 업데이트(markVisitedInSession)되어 hasVisitedInSessionfalsetrue로 변함
  5. handleRedirecthasVisitedInSession를 의존성으로 하는 useCallback으로 감싸짐

상태가 변경되면 React는 새로운 handleRedirect 콜백 식별자를 생성합니다. useEffect는 의존성이 변경된 것을 감지하고 효과를 다시 실행하여 handleRedirect를 다시 호출합니다—이미 페이지가 이동 중임에도 불구하고.

Source:

Root Cause: Safari의 네비게이션 동작

  • Chrome: window.location.href를 설정하면 현재 페이지에서 JavaScript 실행이 즉시 중단됩니다. 두 번째 handleRedirect는 실행되지 않습니다.
  • Safari: 네비게이션이 진행 중에도 JavaScript가 계속 실행됩니다. 두 번째 호출이 발생하여 API 요청을 시작하지만, 페이지가 네비게이션 중이므로 요청이 취소됩니다. catch 블록이 오류 페이지를 렌더링하여 “Link Not Found”가 순간적으로 표시됩니다.

수정

우리는 useState 대신 useRef를 사용하여 가드를 도입했고, 이를 통해 리다이렉트 로직이 한 번만 실행되도록 보장했습니다.

// 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 */]);

useRef가 작동하는 이유

  • 리렌더링 없음: ref를 업데이트해도 컴포넌트가 다시 렌더링되지 않으므로 콜백의 정체성이 유지됩니다.
  • 렌더링 간 지속성: ref 값은 렌더링 사이에 그대로 유지되어 이후 호출을 조용히 차단합니다.

가드에 useState를 사용했다면, 값을 true로 설정할 때 또 다른 렌더링이 발생하여 새로운 콜백 정체성이 생성되고 효과가 다시 트리거되는 루프가 발생했을 수 있습니다—우리가 피하고자 했던 상황입니다.

사후

  • Safari, Chrome, Firefox 전반에 걸쳐 수정 사항을 확인했습니다.
  • 디버그 계측(로컬스토리지 로깅 및 디버그 패널)을 제거했습니다.
  • useRef 가드를 유지하여 이 Safari‑specific 동작에 대한 세 줄 방어를 적용했습니다.

요점

  • 브라우저 네비게이션 차이: Chrome은 window.location.href에서 JavaScript 실행을 중단하지만 Safari는 그렇지 않다. 이는 어느 브라우저의 버그가 아니라—사양이 언제 실행을 멈춰야 하는지를 규정하지 않는다.
  • React 훅이 숨겨진 루프를 만들 수 있음: 콜백 내부에서 업데이트되는 상태를 포함하는 useCallback + useEffect 의존성은 보이지 않는 재실행을 일으킬 수 있으며, 특히 네비게이션이 개입할 때 그렇다.
  • 필요할 때 프로덕션에 계측 삽입: 버그를 로컬에서 재현할 수 없을 때, 플래그 뒤에 최소한의 계측 코드를 배포하여 실제 디바이스가 문제를 드러내게 한다.
  • 렌더링을 일으키지 않는 상태에는 useRef 사용 권장: 렌더링을 트리거하지 않아야 하는 가변 상태(예: “리다이렉트 여부” 플래그)가 필요할 때 useRef가 적절한 도구이다.

Safari 특유의 JavaScript 동작에 걸린 적 있나요? 댓글로 이야기를 공유해주세요.

Built at jo4.io – 모든 브라우저, 특히 Safari에서도 동작하는 URL 단축기.

0 조회
Back to Blog

관련 글

더 보기 »