개발일지/Next.js

[Client-side Navigation] Next.js 13에서 페이지 이동 및 새로고침 감지하기

ayeongjin 2025. 3. 1. 01:06

🏃‍♂️ [Client-side Navigation] Next.js 13에서 페이지 이동 및 새로고침 감지하기

 

 

 

Next.js를 사용하여 일기 작성 기능을 개발하면서, 작성 도중 페이지를 벗어나려 할 때 임시 저장을 유도하는 모달을 추가해야 했다.

Next.js 12까지는 next/router를 사용해서 쉽게 구현 가능하다던데, Next.js 13부터 next/navigation을 사용하게 되면서 next/router의 이벤트 감지 기능이 사라져, 페이지 이동을 직접 감지하는 방법을 찾아야 했다.

 

 

💡 목표

  • 사용자가 일기 작성 중에 페이지를 벗어나려 할 때 확인 모달을 띄운다.
  • Next.js 13에서 router.push, router.replace, popstate, beforeunload 등을 활용하여 직접 페이지 이동을 감지한다.

 


 

1) 뒤로 가기 감지 (popstate 이벤트 활용)

 

popstate 이벤트

  • 사용자가 뒤로 가기 버튼을 눌렀을 때 발생하는 이벤트이다.
  • popstate를 활용하면 사용자가 뒤로 가기를 시도할 때 모달을 띄우고, 이동을 막을 수 있다.

동작 방식

  • 처음 렌더링될 때 history.pushState(null, '', location.href)를 실행하여 현재 페이지를 다시 push한다
    이렇게 하면 사용자가 뒤로 가기를 눌렀을 때 브라우저의 기본 동작을 막고 우리가 직접 처리할 수 있다.
  • popstate 이벤트를 감지하여 작성 중이면 모달을 띄우고, 아니라면 뒤로 이동을 허용한다.
useEffect(() => {
  // 페이지가 처음 렌더링될 때 현재 상태를 push하여 뒤로 가기를 방지
  if (isFirstRender.current) {
    history.pushState(null, "", location.href);
    isFirstRender.current = false;
  }
}, []);

useEffect(() => {
  const handlePopState = () => {
    if (isWriting) {
      setIsOpen(true); // 작성 중이면 모달을 띄움
    } else {
      router.back(); // 작성 중이 아니면 정상적으로 뒤로 가기 실행
    }
  };

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

 

 

2) 새로고침 감지 (beforeunload 이벤트 활용)

 

beforeunload 이벤트

  • 사용자가 페이지를 새로고침하거나 닫으려고 할 때 발생하는 이벤트이다.
  • returnValue를 설정하면 브라우저가 "이 페이지를 벗어나시겠습니까?" 같은 기본 경고창을 띄운다.

동작 방식

  • beforeunload 이벤트를 감지하여 작성 중이면 경고 메시지를 표시한다.
  • 브라우저가 자체적으로 제공하는 기능을 활용하여 새로고침 시 확인을 받을 수 있도록 한다.
useEffect(() => {
  const handleUnload = (e: BeforeUnloadEvent) => {
    if (isWriting) {
      e.preventDefault();
      e.returnValue = ""; // 크롬 기준으로 빈 문자열을 설정해야 경고창이 뜸
    }
  };
  
  window.addEventListener("beforeunload", handleUnload);
  return () => {
    window.removeEventListener("beforeunload", handleUnload);
  };
}, [isWriting]);

 

3) router.push 및 router.replace 오버라이드로 페이지 이동 감지

 

Next.js 13에서 router.push, router.replace 감지가 필요한 이유

  • next/routerrouteChangeStart 이벤트가 제거되어 페이지 이동을 감지할 수 없음
  • 따라서, router.pushrouter.replace를 직접 오버라이드하여 감지하는 방식이 필요함

동작 방식

  • router.push, router.replace기존 함수로 감싸서 확인 모달을 띄우는 방식으로 변경
  • /write-diary/drawing 페이지로 이동하는 경우에는 예외 처리하여 모달 없이 이동 가능
useEffect(() => {
  const confirmNavigation = (
    originalFunction: (href: string, options?: { scroll?: boolean }) => void,
    href: string,
    options?: { scroll?: boolean }
  ) => {
    if (href === "/write-diary/drawing") {
      originalFunction(href, options); // 특정 페이지 이동 시 예외 처리
      return;
    }

    if (isWriting) {
      setPendingNavigation(() => () => originalFunction(href, options));
      setIsOpen(true); // 작성 중이면 모달 띄우기
      return;
    }
    originalFunction(href, options); // 작성 중이 아니면 정상 이동
  };

  // 기존 push와 replace 함수를 저장
  const originalPush = router.push;
  const originalReplace = router.replace;

  // push와 replace 함수를 감싸서 페이지 이동 감지 기능 추가
  router.push = (href: string, options?: { scroll?: boolean }) =>
    confirmNavigation(originalPush, href, options);
  router.replace = (href: string, options?: { scroll?: boolean }) =>
    confirmNavigation(originalReplace, href, options);

  return () => {
    router.push = originalPush;
    router.replace = originalReplace;
  };
}, [isWriting, router]);

 

 

4) 페이지 이동 감지로 열린 모달 close 조작

글작성중이라 경고 모달이 열렸을떄, 아무런 동작 없이 모달만 닫을 경우 두번째 뒤로가기를 시도할 경우 감지가 되지 않는다.

다시 페이지 처음 렌더링될 떄 처럼 현재 페이지를 push 해준다.

  const onCancel = () => {
    setIsOpen(false);

    // 현재 페이지 상태를 다시 push하여 뒤로가기가 발생했을 때 다시 감지되도록 함
    history.pushState(null, "", location.href);
  };

 

 


 

최종 구현 결과

  • 뒤로 가기, 새로고침, 페이지 이동을 감지하여 모달을 띄우는 기능 구현
  • 사용자는 "임시 저장", "작성 취소", "현재 페이지 유지" 중 선택 가능
  • /write-diary/drawing 페이지로 이동 시 예외 처리하여 모달 없이 이동 가능
  • Next.js 13에서 router.push, router.replace을 직접 오버라이드하여 페이지 이동 감지 가능

Next.js 13에서는 next/router의 기능이 없어 페이지 이동 감지가 어려웠지만, popstate, beforeunload, router.push 오버라이드를 활용하여 해결할 수 있었다.