회원가입, 글쓰기 등 입력 Form 페이지에서 실수로 다른 링크를 클릭하거나, 저장하는 것을 깜빡하고 다른 페이지로 이동하면 유저 입장에서 무척 짜증나는 상황이 된다. 처음부터 폼을 다시 작성하거나 수정해야 하기 때문이다. 임시 저장 기능이 있다면 괜찮지만, 그렇지 않다면 페이지 이탈에 대한 Confirm 단계를 추가해서 사용성을 개선할 수 있다. 실제로 여러 웹 서비스에서 Form 페이지 이탈시 ‘저장하지 않은 내용은 삭제된다’는 안내 문구를 띄운다.

구현 방법


NextJS 자체적으로 여러 라우트 이벤트를 제공하는데 routeChangeStart는 라우트 변경을 시작할 때 트리거되는 이벤트다. 페이지를 언로드(새로고침)할 땐 window 객체에서 발생하는 beforeunload 이벤트를 이용하면 된다. 대략 아래 4가지 단계를 통해 구현한다.

단계별 과정

<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" /> router.replace는 history stack에 있는 가장 마지막 요소를 이동할 경로로 대체한다. 즉 현재 페이지를 새로운 페이지로 덮어쓴다. 그 외엔 router.push와 동일하다. — 참고 노트

  1. 유저가 Form을 저장하지 않은 상태에서 다른 페이지 이동(라우트 변경) 시도
    1. ⚠️ Prev/Next 버튼을 누른 시점에 이동할 페이지 주소로 주소창 경로가 바뀐다
    2. 🗄 때문에 라우트 변경 취소를 대비해 현재 경로를 임시 저장해둔다
  2. routeChangeStart 이벤트 핸들러 실행
  3. 유저의 선택에 따라 아래 값을 반환하는 callback 실행
    1. 확인 true : 다른 페이지로 이동(페이지 이탈)
    2. 취소 false : 페이지 이동 취소
  4. 유저가 취소 버튼을 눌러 현재 페이지에 머물기로 결정했다면…
    1. 1-b 에서 임시 저장한 경로로 복구 — *router.replace(...)*
    2. routeChangeError 이벤트를 발생시켜서 라우트 변경 취소

주의할 점

임시 저장한 경로로 router.replace를 실행하면(4-a) 라우트 변경을 시도하므로 routeChangeStart 이벤트가 발생한다. 그럼 이벤트 핸들러가 실행돼서 위 과정을 또 다시 반복하며 무한 렌더링된다. 이를 방지하려면 라우트 변경을 취소했을 때만 이벤트 핸들러를 실행하도록 해야 한다. 이를 위해 isKilledRouter 같은 내부 상태로 라우트 변경 취소 여부를 관리한다.

isKilledRouter 값 (라우트 변경 취소 여부) routeChangeStart 핸들러 실행 여부
true
false

유저가 페이지 이동을 취소한 후 다시 페이지 이동을 시도하는 상황도 고려해야 한다. 즉, 1~4번 단계까지 한 사이클 실행을 마친 뒤에도 다음 사이클에서 이벤트 핸들러가 실행될 수 있도록 isKilledRouter 상태를 false로 바꿔주는 작업이 필요하다.

구현 하기


router.replace 사용

<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" /> 참고 내용

커스텀훅이 받는 shouldStopNavigation 파라미터가 true라면 routeChangeStart 이벤트 핸들러가 등록된다. 그 후 유저가 페이지 이동(라우트 변경)을 시도하면 onRouteChange 이벤트 핸들러를 실행한다.