로잇의 회원가입 페이지는 총 3단계로 이루어져 있다.
회원가입을 완료하기 전에 사용자가 브라우저의 뒤로 가기 버튼을 누르면 기존에 입력한 데이터가 사라질 위험이 있다.
특히, 이메일 인증 과정이나 개인정보 작성을 진행하던 중 실수로 뒤로 가기를 누르면, 사용자는 다시 처음부터 입력해야 하는 불편함을 겪게 된다.
이러한 불편함이 반복되면 사용자 이탈률이 증가할 가능성이 높다고 판단했다.
따라서 사용자가 실수로 브라우저 뒤로 가기를 눌러도 기존 입력 데이터가 초기화되지 않도록 보호하는 방법이 필요했다.
이를 해결하기 위해, 뒤로 가기 버튼을 눌렀을 때 경고 모달을 띄우고, 사용자가 정말로 뒤로 갈지 선택할 수 있도록 하는 기능을 구현하기로 했다.
router.beforePopState
활용Next.js에서는 router.beforePopState
를 활용하면 브라우저의 뒤로 가기 이벤트를 감지하여 원하는 동작을 실행할 수 있다.
이를 활용해 다음과 같은 기능을 구현할 수 있다.
이 글에서는 이 기능을 구현하는 과정에서의 시행착오와 최종 해결 방법을 기록하고,
Next.js에서 브라우저의 뒤로 가기 동작을 제어하는 방법을 정리해보려고 한다.
router.beforePopState
Next.js 공식 문서에서는 router.beforePopState
를 활용하면 브라우저의 popstate 이벤트가 발생하기 전에 특정 동작을 실행할 수 있다고 설명하고 있다.
router.beforePopState(cb)
란?router.beforePopState(cb)
는 브라우저에서 popstate 이벤트(뒤로 가기, 앞으로 가기 등)가 발생할 때 실행되는 콜백 함수를 등록하는 메서드다.
속성 | 설명 |
---|---|
url | 뒤로 가기 후 이동할 라우트 (예: /about → /home으로 이동하는 경우 url = "/home") |
as | 브라우저에 표시될 실제 URL (예: /home?ref=google 등) |
options | router.push() 또는 router.replace() 에서 전달된 추가 옵션 |
→ 이를 이용하면 사용자가 뒤로 가기를 눌렀을 때 Next.js가 자동으로 이전 페이지로 이동하는 것을 막고, 원하는 동작을 실행할 수 있을 거라고 판단했다.
router.beforePopState
를 활용한 뒤로 가기 차단처음에는 단순하게 “뒤로 가기 버튼을 누르면 popstate
이벤트가 발생하니까, 이를 감지해서 beforePopState
에서 모달을 띄우면 되겠지?” 라고 생각했다.
그래서 아래와 같은 코드를 작성했다.
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useModalStore } from "@/stores/modalStore";
export default function useSignupNavigation() {
const router = useRouter();
const { openModal } = useModalStore();
useEffect(() => {
router.beforePopState(() => {
// 모달을 띄운다
openModal("confirmNavigationBack");
return false; // 뒤로 가기 동작을 막는다
});
return () => {
router.beforePopState(() => true); // cleanup
};
}, [router, openModal]);
return {};
}
실행하기 전까지 이 코드가 다음과 같이 실행할 거라고 생각했다.
사용자가 브라우저의 뒤로 가기 버튼을 누른다.
router.beforePopState가 이를 감지한다.
return false;를 반환해 Next.js의 기본 뒤로 가기 동작을 막는다.
모달을 띄워 사용자의 확인을 받는다.
하지만 예상과 다르게 동작했는데,
모달이 뜨면서 URL은 뒤로 가기가 동작하는데, 화면은 그대로 모달이 유지되는 이상한 상황이 발생했다.
이 문제를 해결하기 위해 뒤로 가기 버튼을 눌렀을 때 발생하는 일의 순서를 차례로 정리해봤다.
순서 | 발생하는 이벤트 | 설명 |
---|---|---|
1️⃣ | 사용자가 브라우저의 “뒤로 가기” 버튼을 클릭 | 뒤로 가기 이벤트 발생 |
2️⃣ | 브라우저가 히스토리 스택을 이동 | /pageC → /pageB로 이동 (URL이 변경됨) |
3️⃣ | popstate 이벤트 발생 | window.onpopstate 가 실행됨 |
4️⃣ | Next.js가 URL 변경을 감지 | router.beforePopState() 실행됨 |
5️⃣ | router.beforePopState() 의 콜백이 실행됨 | 개발자가 정의한 로직이 실행됨 |
6️⃣ | return true; 일 경우 | Next.js가 자동으로 이전 페이지를 렌더링 |
7️⃣ | return false; 일 경우 | Next.js의 내부 라우팅은 취소되지만, URL 변경은 이미 발생한 상태 |
분석결과 문제가 되는 부분은 순서 2번으로 return false;
를 사용해도 브라우저 URL은 이미 변경된 상태가 된다는게 원인이었다.
window.history.pushState()
로 URL을 되돌리기사용자가 뒤로 가기 버튼을 클릭하면 브라우자가 히스토리 스택을 이동하며 URL이 변경되는데 이는 브라우저의 기본 동작이라서 막을 수 없다. 따라서 브라우저의 URL 변경을 방지하려면 window.history.pushState()
를 사용하여 변경된 URL을 원래대로 되돌려야 한다.
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useModalStore } from "@/stores/modalStore";
export default function useSignupNavigation() {
const router = useRouter();
const { openModal } = useModalStore();
useEffect(() => {
router.beforePopState(() => {
window.history.pushState(null, "", router.asPath); // URL 변경 방지
openModal("confirmNavigationBack"); // 모달 띄우기
return false; // Next.js의 기본 popstate 동작 차단
});
return () => {
router.beforePopState(() => true); // cleanup
};
}, [router]);
return {};
}
이 코드는 아래와 같은 순서로 동작하는데, 이제 브라우저의 뒤로 가기를 눌러도 URL이 변경되지 않는다.
순서 | 발생하는 이벤트 | 설명 |
---|---|---|
1️⃣ | 사용자가 브라우저의 “뒤로 가기” 버튼을 클릭 | 뒤로 가기 이벤트 발생 |
2️⃣ | 브라우저가 히스토리 스택을 이동 | /pageC → /pageB로 이동 (URL이 변경됨) |
3️⃣ | popstate 이벤트 발생 | window.onpopstate 가 실행됨 |
4️⃣ | Next.js가 URL 변경을 감지 | router.beforePopState() 실행됨 |
5️⃣ | 개발자가 return false; 를 반환 | Next.js의 내부 라우팅이 취소됨 |
6️⃣ | window.history.pushState() 실행 | URL을 다시 /pageC로 되돌림 |
7️⃣ | 모달을 띄움 | 사용자에게 이전으로 돌아갈지 여부를 확인함 |
8️⃣ | 사용자가 “확인”을 선택한 경우 | router.beforePopState(() => true); 실행 후 router.back(); 호출 |
9️⃣ | 뒤로 가기 정상 동작 | 브라우저가 /pageB로 이동 |
router.beforePopState()
를 활용하면 브라우저 뒤로 가기 이벤트를 감지할 수 있다.return false;
를 사용해도 URL은 이미 변경된 상태가 된다.window.history.pushState()
를 사용해 URL 변경을 되돌려야 한다.router.back()
실행할 수 있다.이제, 사용자가 모달에서 “확인”을 눌렀을 때 정상적으로 이전 페이지로 이동하도록 구현하면 된다.
const handleBrowserBack = () => {
router.beforePopState(() => true); // 뒤로 가기 차단 해제
router.back(); // 이전 페이지로 이동
};
router.beforePopState(() => true);
→ 기존에 막아놓았던 뒤로 가기 동작을 해제router.back();
→ 이전 페이지로 이동(이 함수는 사용자가 “이전으로 가기” 모달에서 확인 버튼을 눌렀을 때 실행되도록 함)아래는 최종적으로 뒤로 가기를 감지하여 모달을 띄우고, 확인 버튼을 눌렀을 때 정상적으로 뒤로 가기를 수행하는 코드다.
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useModalStore } from "@/stores/modalStore";
export default function useSignupNavigation() {
const router = useRouter();
const { openModal, closeModal } = useModalStore();
useEffect(() => {
const handlePopState = () => {
window.history.pushState(null, "", router.asPath);
openModal("confirmNavigationBack");
return false;
};
router.beforePopState(handlePopState);
return () => {
router.beforePopState(() => true);
};
}, [router, openModal]);
/**
* '이전으로 가기' 모달에서 확인 버튼 클릭 시 실행되는 함수
* - beforePopState의 반환값을 true로 변경해 뒤로 가기 차단 해제
* - router.back()을 호출해 정상적으로 이전 페이지로 이동
*/
const handleBrowserBack = () => {
router.beforePopState(() => true);
router.back();
};
return {
handleBrowserBack,
closeModal,
};
}