회원가입 중 실수 방지: Next.js에서 뒤로가기 UX 보호 설계

김하은·2025년 2월 24일
0

Next.js

목록 보기
3/3

코드 작성 계기

로잇의 회원가입 페이지는 총 3단계로 이루어져 있다.

회원가입을 완료하기 전에 사용자가 브라우저의 뒤로 가기 버튼을 누르면 기존에 입력한 데이터가 사라질 위험이 있다.

특히, 이메일 인증 과정이나 개인정보 작성을 진행하던 중 실수로 뒤로 가기를 누르면, 사용자는 다시 처음부터 입력해야 하는 불편함을 겪게 된다.

이러한 불편함이 반복되면 사용자 이탈률이 증가할 가능성이 높다고 판단했다.

따라서 사용자가 실수로 브라우저 뒤로 가기를 눌러도 기존 입력 데이터가 초기화되지 않도록 보호하는 방법이 필요했다.

이를 해결하기 위해, 뒤로 가기 버튼을 눌렀을 때 경고 모달을 띄우고, 사용자가 정말로 뒤로 갈지 선택할 수 있도록 하는 기능을 구현하기로 했다.

해결 방법 – router.beforePopState 활용

Next.js에서는 router.beforePopState를 활용하면 브라우저의 뒤로 가기 이벤트를 감지하여 원하는 동작을 실행할 수 있다.

이를 활용해 다음과 같은 기능을 구현할 수 있다.

  • 뒤로 가기 이벤트 감지 – 특정 조건에서만 뒤로 가기 차단
  • 뒤로 가기를 차단하고 모달을 띄우기 – 사용자가 실수로 뒤로 가기를 눌렀을 때 경고 모달 표시
  • 사용자가 확인을 누르면 정상적으로 이전 페이지로 이동하기

이 글에서는 이 기능을 구현하는 과정에서의 시행착오와 최종 해결 방법을 기록하고,

Next.js에서 브라우저의 뒤로 가기 동작을 제어하는 방법을 정리해보려고 한다.

Next.js의 공식 문서 분석 – router.beforePopState

Next.js 공식 문서에서는 router.beforePopState를 활용하면 브라우저의 popstate 이벤트가 발생하기 전에 특정 동작을 실행할 수 있다고 설명하고 있다.

router.beforePopState(cb)란?

router.beforePopState(cb)는 브라우저에서 popstate 이벤트(뒤로 가기, 앞으로 가기 등)가 발생할 때 실행되는 콜백 함수를 등록하는 메서드다.

  • 콜백 함수 cb는 다음과 같은 객체를 매개변수로 받는다.
    속성설명
    url뒤로 가기 후 이동할 라우트 (예: /about → /home으로 이동하는 경우 url = "/home")
    as브라우저에 표시될 실제 URL (예: /home?ref=google 등)
    optionsrouter.push() 또는 router.replace()에서 전달된 추가 옵션
  • cb가 false를 반환하면 Next.js의 기본 popstate 동작이 실행되지 않는다.

→ 이를 이용하면 사용자가 뒤로 가기를 눌렀을 때 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 {};
}

실행하기 전까지 이 코드가 다음과 같이 실행할 거라고 생각했다.

  1. 사용자가 브라우저의 뒤로 가기 버튼을 누른다.

  2. router.beforePopState가 이를 감지한다.

  3. return false;를 반환해 Next.js의 기본 뒤로 가기 동작을 막는다.

  4. 모달을 띄워 사용자의 확인을 받는다.

하지만 예상과 다르게 동작했는데,

모달이 뜨면서 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,
  };
}

참고 자료

profile
아이디어와 구현을 좋아합니다!

0개의 댓글

관련 채용 정보