본문 바로가기

공부

[Next.js] 브라우저 뒤로가기 막기 beforePopState 문제점

최근 웹뷰 서비스를 개발 중에

뒤로가기 처리를 고민하게 되었다.

 

버튼을 통한 뒤로가기는 자바스크립트 코드로 막으면 그만이지만

브라우저 자체에서 발생하는 이벤트가 문제였다.

 

웹뷰에서는 사용자가 손가락으로 옆으로 스위핑(뒤로가기) 를 하게 되면

popstate 이벤트가 발생한다.

 

popstate 이벤트의 문제점은

새로고침이나 닫기 이벤트와 다르게 취소가 불가능한 것이다.

preventDefault 같은 것도 먹히지 않는다.

 

next.js에서는 popstate 이벤트를 제어하기 위한 메서드로

beforePopState를 제공한다.

뒤로가기 이벤트가 발생하면 실행되는 콜백을 전달하는 메서드이다.

https://nextjs.org/docs/api-reference/next/router#routerbeforepopstate

 

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // I only want to allow these two routes!
      if (as !== '/' && as !== '/other') {
        // Have SSR render bad routes as a 404.
        window.location.href = as
        return false
      }

      return true
    })
  }, [])

  return <p>Welcome to the page</p>
}

예시코드도 심플하다.

 

return false를 하면 화면이 이동되지 않는다.

 

즉, 뒤로가려는 경우 return false를 줘서 뒤로가기를 막는 것을 생각해볼 수 있다.

 

하지만 처음에 설명했듯이 popstate 이벤트는 취소가 불가능하다.

 

따라서 화면이 이동되는 것은 막을 수 있지만 결국엔 history stack은 제거된다.

 

결론을 말하자면 다음과 같이 처리하면 된다.

 

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      if (as !== router.asPath) {
        window.history.pushState('', '');
        router.push(router.asPath);
        return false
      }

      return true
    })
    return () => {
       router.beforePopState(() => true);
    };
  }, [])

  return <p>Welcome to the page</p>
}

window.history에 직접 pushState를 하고

이후에 router.push로 현재 브라우저에 표시된 url(asPath)를 주면 히스토리 스택을 유지한 채로 뒤로가기를 취소시킬 수 있다.

 

이것까진 알아둘 필요는 없지만

한가지 의문이 들 수도 있다.

router.push도 window.history.pushState를 내부적으로 호출하는데

왜 router.push 대신 window.history를 직접 건드릴까?

 

그 이유는

next.js의 내부 처리에 있었다.

 

https://github.com/vercel/next.js/blob/db3b8446d750dd24ca61912472e07e6c69d64400/packages/next/shared/lib/router/router.ts#L222

 

next.js의 router.push는 내부적으로 pushState를 할지, replaceState를 할지 고려한 후 결정한다.

따라서 우리가 원하는 pushState가 아니라 replaceState로 처리되어 원하지 않던 히스토리 구조가 생기게 된다.