🏃♂️ [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/router의 routeChangeStart 이벤트가 제거되어 페이지 이동을 감지할 수 없음
- 따라서, router.push와 router.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 오버라이드를 활용하여 해결할 수 있었다.