새로고침, 페이지 이탈, 브라우저 앞/뒤로 가기 버튼 클릭 각각 감지하기

dobby·2025년 2월 25일
1
post-thumbnail

웹으로 만든 채팅 토론 서비스에서 새로고침시 재입장 로직을 자동 실행시켜주려고 한다.
또한 페이지 이탈시(URL 직접 입력으로 페이지 이동) 확인 창을 띄워주고자 한다.
위 기능을 추가하면서 사용한 새로고침, 페이지 이탈에 대해 각각 다르게 감지한 방법을 작성하고자 한다.


페이지 이탈 감지하기

페이지 이탈을 감지하는데 널리 알려진 이벤트로는 beforeunload 이벤트가 있다.
이 이벤트는 새로고침, 페이지 이탈, 탭 닫기 등 unload 이벤트가 발생하기 전에 유저에게 정말로 새로고침/페이지이탈 할 것인지 확인 창을 띄워줄 수 있도록 한다.

즉, beforeunload 이벤트 -> OK -> unload 이벤트 순으로 발생한다.

일단 새로고침이든 페이지 이탈이든 한 번 실행하고 나면 상태 데이터가 사라질 수 있는 위험이 있기에
경고차 확인 창을 띄우고자 했다.

beforeunload 이벤트 추가하기

커스텀 훅을 만들어 useEffect로 이벤트를 추가해주었다.

  const handleUnload = useCallback(() => {
    webSocketClient?.deactivate(); // 소켓 연결 끊기
    mutation?.(); // 채팅방 나가기 API 호출
  }, [webSocketClient, mutation]);

  const handleBeforeUnload = useCallback((e: BeforeUnloadEvent) => {
    e.preventDefault(); // 브라우저에서 정말 새로고침/페이지이탈 할 것인지 묻는 확인 창이 뜨게 된다.
  }, []);

  useEffect(() => {
    if (window) {
      window.addEventListener('beforeunload', handleBeforeUnload);
      window.addEventListener('unload', handleUnload);
    }
    return () => {
      if (window) {
        window.removeEventListener('beforeunload', handleBeforeUnload);
        window.removeEventListener('unload', handleUnload);
      }
    };
  }, [mutation, handleUnload, handleBeforeUnload]);

이렇게 작성하고 사용하고자 하는 페이지에 추가해주면 된다.

beforeunload , unload 이벤트는 router 이벤트로는 실행되지 않는다.
유저가 브라우저상에서 제공하는 기능으로 페이지 이탈을 시도할 때 실행된다.!
이점을 참고하고 사용하면 된다.

mov -> gif 변환시 느려지는거 속 터지네


새로고침 감지하기

새로고침도 beforeunload로 감지할 수 있지만, 새로고침은 결국 그 페이지에 남겠다는 것이다.
그래서 페이지 이탈과는 조금 다르다고 생각했고, 상태 초기화로 인해 유저가 재입장 해야하는 번거로움을 겪어야 하는게 싫었다.

그래서 새로고침으로 채팅방에 다시 들어오게 될 땐, 유저 정보를 가지고 있다가 재입장 API를 자동으로 호출하기로 했다.
그 과정에서 새로고침 감지가 필요했다.

이때 사용할 수 있는 코드는 다음과 같다.

performance.getEntriesByType('navigation')[0]

이 값은 4가지 중 하나이다.

  • back_forward (뒤로가기로 페이지에 접근할 때)
  • navigate (a태그, location.href로 페이지 넘어올 때)
  • reload (새로고침으로 페이지 접근할 때)
  • prerender (다음 라우팅을 위해 페이지 리소스가 필요할 수 있다는 힌트를 브라우저에게 전달해서 가져올 때)

혹시 back_forward를 사용하려고 한다면, 아래의 포스트를 읽어본 후 사용하길 권한다.
window.performance back_forward 이벤트 미발생 오류, 해결


새로고침은 reload를 사용하면 된다.
여기서, 타입스크립트는 그냥 사용하려고 하면 인식을 못해 에러가 발생한다.

const entries = performance.getEntriesByType(
        'navigation',
      )[0] as PerformanceNavigationTiming;

이렇게 선언해주고 사용하면 에러가 발생하지 않는다.

새로고침을 감지할 페이지에 아래 코드를 넣어주면 된다.

useEffect(() => {
    if (typeof window !== 'undefined') {
      const entries = performance.getEntriesByType(
        'navigation',
      )[0] as PerformanceNavigationTiming;

      if (entries?.type === 'reload') {
        // 코드
      }
    }
  }, []);

브라우저 뒤로가기/앞으로 가기 버튼으로 페이지 이탈 감지

  // 브라우저 뒤로가기 버튼 클릭 시 페이지 이탈 방지 모달 띄우기
  useEffect(() => {
    const handlePopState = (event: PopStateEvent) => {
      event.preventDefault();
      window.history.pushState(null, '', window.location.pathname); // 뒤로가기 무효화
      handleBack();
    };

    window.history.pushState(null, '', window.location.pathname); // 현재 상태 추가
    window.addEventListener('popstate', handlePopState);

    return () => {
      window.removeEventListener('popstate', handlePopState);
    };
  }, []);

브라우저 상단에 위차한 앞으로가기/뒤로가기 버튼 클릭에 대한 이벤트를 넣고 싶을 때 위 코드를 사용하면 된다.
기존 이벤트를 무효화한 뒤 나는 정말 나갈 것인지 묻는 커스텀 창을 띄워주었다. (handleBack)


채팅방에 적용하기

나는 두 가지 기능을 추가해주어야 했다.
1. 새로고침 시 재입장 API 호출 (reload)
2. URL 직접 변경으로 채팅방에 접근할 때 막기 (navigate)

일단 사용자 정보와 채팅방 정보를 가지고 있어야 재입장 API를 호출할 수 있으니, 데이터를 가지고 있어야 한다.
이를 위해 zustand의 persist를 사용했다.
sessionStorage에 데이터를 저장해 탭간 데이터 공유를 막고, 새로고침시에도 데이터가 날아가지 않도록 해주었다.


하지만 상당히 까다로운게, storage에 저장되는 것이니 페이지에서 강제로 나갈 때, 정상적으로 나갈 때, 페이지 접근 등 storage 데이터를 초기화하거나 저장하는 등 관리가 쉽지는 않았다.

어쩌겠어.. 해야지!

그렇게 작성한 코드는 다음과 같다.

  const isSameAgora = (prevId: number, currentId: string | undefined) => {
    if (prevId === Number(currentId)) {
      return true;
    }
    return false;
  };

  useEffect(() => {
    // 채팅방으로 페이지 이동시 다른 탭에서 이미 입장한 유저라면(session storage 데이터 없음) 내보내기
    if (
      typeof window !== 'undefined' &&
      !isNull(session) &&
      !hasMutated.current
    ) {
      const entries = performance.getEntriesByType(
        'navigation',
      )[0] as PerformanceNavigationTiming;

      if (entries?.type === 'reload') {
        // 입장하기 api 호출
        hasMutated.current = true;
        mutation.mutate();
      }

      if (entries?.type === 'navigate' && isNull(agoraTitle)) {
        // 채팅방 입장하기 페이지 띄우기
        window.location.replace(`/flow/enter-agora/${agoraId}`);
      } else if (
        entries?.type === 'navigate' &&
        !isSameAgora(prevAgoraId, agoraId)
      ) {
        // 채팅방에서 다른 채팅방으로 이동, storage 데이터 초기화 후 입장하기 페이지 띄우기
        agoraInfoReset();
        userProfilReset();
        selectedAgoraInfoReset();

        useAgora.persist.rehydrate();
        useEnter.persist.rehydrate();

        window.location.replace(`/flow/enter-agora/${agoraId}`);
      }
    }
  }, [session]);

hasMutated 은 로직 중복 실행을 방지하기 위해 추가해주었다.

이렇게 하면 새로고침 시에도 유저가 재입장을 해야하는 번거로움이 줄어든다!
그리고 다른 채팅방에서 강제로 넘어오려고 할 때도 막을 수 있다.

profile
성장통을 겪고 있습니다.

0개의 댓글

관련 채용 정보