ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • nextjs 스크롤 저장 기억하기
    개발 2022. 8. 9. 20:00

    일반적으로 뒤로가기 버튼을 클릭 할 때 유저는 현재 페이지에 진입하기 직전에 내가 있던 상태로 되돌아가는 것을 기대한다. 상태에는 내가 이전에 있던 페이지 경로 뿐만 아니라 보고 있었던 스크롤 위치도 포함된다. 그래서 아래 사이트처럼 내가 스크롤했던 위치를 잃어버리는 경우는 유저 경험에 크게 좋지 못한 케이스에 해당한다. 이런 상태로라면 책 팔기가 쉽지 않을 것 같다.

     

     

    왜 이런 일이 발생하는 걸까?

     

    앱개발을 했을 때는 새로운 화면 진입점마다 새로운 창을 만들어 줬기 때문에 이전 화면에서 읽어온 데이터와 스크롤 위치는 이전의 창에 그대로 저장돼 있어 딱히 상태 저장문제를 고려하지 않아도 됐다. 그런데 nextjs 에서 화면 이동시 사용하는 router.push 는 Activity와 달리 path만 이동하는 개념이었다. 그래서 뒤로가기를 하는 경우 이전에 어느 path를 거쳐왔는지만 알 수 있을 뿐 어떤 데이터가 쌓여 있었는지는 기억하지 못한다. nextjs에서는 이전의 경로로만 이동하는 것일 뿐 새롭게 데이터를 로드하는 작업이 포함되기 때문에 해당 페이지에 처음 진입하는 것과 똑같은 상태가 된다.

     

    어떻게 해결 할 수 있을까?

     

    앱개발에서 사용했던 방법처럼 클릭시 새로운 탭을 만들어서 별도의 창으로 분리하는 방법이 있을 것인데 이건 요구사항을 바꾸는 일이라 좋은 해결책이 될 수는 없을 것이다. 그리고 새로운 창을 만든다는 것은 뒤로가기가 안되고 유저가 직접 창을 꺼야하는 불편함도 생길 수 있다.

     

    내가 구현한 방법은 진입 직전에 현재까지 로딩된 데이터와 스크롤을 기억하는 방식이다. sessionStorage 를 사용하면 해당 세션간에만 저장할 데이터를 보관할 수 있다. 

     

    <div
        onClick={() => {
            sessionStorage.setItem(booksKey(), JSON.stringify(books))
            sessionStorage.setItem(scrollKey(), `${window.scrollY}`)
    
            router.push(`/scroll/book/${book.isbn13}`)
        }}

     

    그리고 해당 페이지에 다시 돌아오는 경우 이 정보를 이용해서 값을 복원했다.  sessionStorage에 저장된 값이 있다면 그 값을 사용하고 어떤 이유로 값을 잃어버렸다면 새롭게 로드한다.

     

    useEffect(() => {
        const value = JSON.parse(sessionStorage.getItem(booksKey()))
    
        if (value !== null && value.length > 0) {
            setBooks(value)
        } else {
            axios.get('https://api.itbook.store/1.0/new')
                .then((res) => {
                    setBooks(res.data.books)
                })
        }
    }, [])
    
    useEffect(() => {
        const scroll = parseInt(sessionStorage.getItem(scrollKey()), 10)
        window.scrollTo(0, scroll)
    }, [books])

     

    그 결과 뒤로가기를 했을 때 이전에 스크롤한 위치로 돌아가는 것을 확인할 수 있다.

     

     

    그러나 이 방식은 책 페이지 리스트로 진입이 뒤로가기인지 그냥 들어온 것인지 알 수 없기 때문에 완전하지 않다. 그래서 페이지를 리프레시해도 나는 계속 스크롤 위치가 고정되는 문제가 발생한다. 뒤로가기에만 스크롤을 복원해야하는데 모든 경우에 스크롤을 복원하는 문제가 생기고 말았다.

     

     

    해결하기 위해선 문제점이었던 현재 페이지가 뒤로가기로 진입했는지 그렇지 않은지를 구분할 수 있어야 한다. 이 방법은 https://gist.github.com/claus/992a5596d6532ac91b24abe24e10ae81 여기 코드를 활용했다. 원래 이 코드는 이 포스트의 본 목적인 스크롤을 복원시켜주는데 사용하는 코드인데 예제 사이트처럼 높이가 고정되지 않은 페이지에는 사용할 수 없었다. 대신 이 코드를 어떤 페이지가 뒤로가기로 보이는 페이지인지를 확인하는 용도로 썼다. redux를 이용해서 다른 페이지에서도 이 정보를 쉽게 확인할 수 있도록 만들었다. 

     

     

    GitHub - kwony/nextjs-study: nextjs, css,

    nextjs, css, . Contribute to kwony/nextjs-study development by creating an account on GitHub.

    github.com

     

    그리고 책 리스트 페이지에 복원 페이지가 현재 경로와 일치하는지 아닌지를 판단하는 조건문을 추가했다. 그 결과 정상적으로 동작하는 것을 확인할 수 있었다.

     

    const restorePage = useSelector((state) => state.restorePage.path)
    
    useEffect(() => {
        const value = JSON.parse(sessionStorage.getItem(booksKey()))
        if (restorePage === router.asPath && value !== null && value.length > 0) {
            setBooks(value)
        } else {
            axios.get('https://api.itbook.store/1.0/new')
                .then((res) => {
                    setBooks(res.data.books)
                })
        }
    }, [])
    
    useEffect(() => {
        if (restorePage === router.asPath) {
            const scroll = parseInt(sessionStorage.getItem(scrollKey()), 10)
            window.scrollTo(0, scroll)
        }
    }, [books])

     

    한계

     

    물론 이 방식도 완벽하진 않다. 내가 책 리스트 페이지를 여러번 거친 경우 매번 sessionStorage에 새롭게 갱신되기 때문에 이전에 읽은 스크롤 위치를 기억하진 못하게 되고 복원하지 못하는 문제가 발생한다. 현재 router의 depth 정보까지 추가해서 해결할 수 있긴 하지만 5MB가 제한인 sessionStorage를 마구 쓰는 것도 정답이 아닐듯 해서 나머지는 UX 적으로 푸는게 좋을 것 같다.

     

    내가 구현한 방식은 복잡하긴 하다. 매번 페이지에 저렇게 스크롤과 데이터를 복원하는 코드를 넣을수도 없는 일이고. 좀더 깔끔한 해결책을 찾아봤는데 나오지 않아서 일단 이렇게만 구현해뒀다. 공식 nextjs 사이트에서 이 문제를 길게 논의한 쓰레드가 있는데 한번 읽어보면 해결에 도움이 될 것 같다. 

     

     

     

    Opt out of scroll restoration when using browser back buttons · Issue #20951 · vercel/next.js

    Describe the feature you'd like to request There is a clear way to opt out of Next.js scroll restoration when using Link or router.push: just pass { scroll: false }. However, I haven't foun...

    github.com

     

    '개발' 카테고리의 다른 글

    typescript interface/type  (0) 2022.09.07
    REST, REST API, RESTful  (0) 2022.09.02
    지긋지긋한 CORS error. 이제 제대로 공부해보자.  (0) 2022.07.30
    css - flex  (0) 2022.07.11
    css - position  (0) 2022.07.10

    댓글

Designed by Tistory.