[Next.js] 뒤로가기 해도 무한스크롤 위치 고정하기

[Next.js] 뒤로가기 해도 무한스크롤 위치 고정하기

·

2 min read

지난 포스트에서 검색 기능을 수정하면서 마지막에 해결하지 못한 문제가 있다. 이 문제를 해결하기 위해 이미 무한 스크롤이 구현된 사이트를 찾다가 포켓몬 공식 사이트를 발견하게 되었다.

포켓몬 공식 사이트에 내가 원하는 스크롤 구현이 완벽하게 되어 있었다.

  1. 무한 스크롤

  2. 아이템 클릭 시 상세 페이지로 이동

  3. 뒤로가도 스크롤 위치 및 데이터 유지

내가 겪고 있는 문제는 3번이다. 상세 페이지에서 뒤로가기하면 첫 번째 페이지로 초기화되는 것이다.

이제 포켓몬 사이트를 살펴보며 어떻게 구현되어있는지 살펴보자.

🟡 포켓몬 공식 사이트

포켓몬 도감의 URL은 /pokedex 이다.

스크롤을 내려 29번 포켓몬을 클릭하니 상세 페이지로 이동하였다. 이때 URL은 pokedex/view/45?word=&characters=&area=&snumber=1&snumber2=1025&typetextcs=&sortselval=number%20asc,number_count%20asc 이다.

다시 뒤로갔다. 아까 스크롤한 위치 그대로 유지되어 있다. URL은 pokedex#pokedex_45 으로, 처음 URL 뒤에 id가 추가되었다.

아이템 각각에 id를 부여하고 상세 페이지에서 뒤로가기 할 때 해당 id를 URL 뒤에 추가해서 해당 위치로 돌아갈 수 있도록 구현한 것이라고 추측하였다.

포켓몬 사이트의 상세 코드는 알지 못하지만 이러한 근거를 토대로 내 방식대로 구현해보았다.

🟢 라이북러리

우선, 목록의 각 아이템 역할을 하는 BookCard 컴포넌트에 id를 부여한다.

function BookCard({ isbn, title, author, cover, route = '' }: BookCardProps) {
  return (
    <li className="book-card" key={title} id={isbn}> ... </li>
  )
}

도서 상세 페이지에 해당하는 컴포넌트에서 popstate가 발생할 때 이벤트를 적용한다. 이때 이벤트는 컴포넌트가 렌더링되고 한 번만 사용할 것이기 때문에 사용 후 제거해줘야 한다.

function AddBook({ id }: AddBookProps) {
  useEffect(() => {
    window.addEventListener('popstate', handlePopState)

    return () => {
      window.removeEventListener('popstate', handlePopState)
    }
  }, [])
}

handlePopState 함수는 이전으로 돌아갈 URL에 현재 선택된 도서의 id를 붙여주는 역할을 한다.

현재 URL 파싱해 URL 객체를 생성하고, URL의 해시 부분에 id가 포함되어 있는지 확인한다. 만약 포함되어 있지 않다면, id를 해시 값으로 지정하고 router.replace로 브라우저 주소의 표시줄을 변경한다. shallow 옵션은 페이지를 다시 불러오지 않고 주소만 업데이트하는 것이다. 이를 통해 페이지가 바뀔 때마다 컴포넌트가 렌더링 되는 것을 방지하여 성능 문제를 방지할 수 있다.

  const handlePopState = () => {
    const backUrl = new URL(window.location.href)
    if (!backUrl.hash.includes(id)) {
      backUrl.hash = id
      router.replace(backUrl.toString(), { shallow: true })
    }
  }

포켓몬 사이트처럼 이전 스크롤 위치와 완전히 동일하진 않지만, 사용자가 선택했던 아이템이 화면에 보이도록 구현하였다.