[React,Next.js] 서버사이드 렌더링(SSR + SSG), 무한스크롤(useSWRInfinite, IntersectionObserver), 로컬스토리지( localStorage)

이태헌·2023년 6월 19일
1
post-thumbnail

개요

Next.js에서 SSR + SSG로 외부 API로 데이터를 받아온 후 로드를 한 이후에 IntersectionObserver로 div를 감지한 후 무한스크롤을 적용시키고 받아온 데이터를 클릭했을떄 세부 페이지로 이동 후 뒤로갔을때 마지막으로 스크롤 한 위치로 이동하기까지의 과정이다.

1. SSR로 API 데이터 받아오기

//index.js

export async function getStaticProps(){

          const [popularMovie , playingMovie] = await Promise.all([
            fetch(`https://api.themoviedb.org/3/movie/popular?api_key=${API_BASE_URL}&page=1`),
            fetch(`https://api.themoviedb.org/3/movie/now_playing?api_key=${API_BASE_URL}`)
          ]) //async, await로 여러개의 API reponse 를 받아와서 저장하기
          const popular = await popularMovie?.json()
          console.log(popular)
          const playing = await playingMovie?.json()
          return { props: {popular,playing}} // 값 넘겨주기
}

먼저 getStaticProps를 이용해서 서버쪽에서 데이터를 받아와 바로 보여준다. 데이터는 themoviedb에서 무료로 제공해주는 데이터를 이용하였다.

SSRSSG를 나누는 요소는 받아온 데이터를 이후에 어떻게 사용할지에 따라서 나뉜다. 만약 CRUD 형식으로 받아온 데이터의 변화가 일어난다면 SSR(getServerSIdeProps)로 데이터를 받아와 뿌려준후 변경사항에 대해서 처리를 해주고 한번만 받아와서 데이터를 사용한다면 SSG(getStaticProps)를 이용하여 데이터를 뿌려준다.

2. 받아온 데이터 메인 화면에 넘겨주기


export default function Home({popular,playing}) {

  const urlTest = `https://api.themoviedb.org/3/movie/popular?api_key=${API_BASE_URL}&page=`
  
  useEffect(()=>{
    if(!popular.results){
      alert("서버 오류 잠시 후 다시 시도해주세요");
      return;
    }
  },[]) //받아온 데이터 없을시 오류 메시지 리턴
  
  return (
    <div>
      <div>
      <MainHeader/>
      </div>
      <div className="container">
        <InfiniteDataList queryKey = {urlTest} initialData ={popular.results}/> // 무한스크롤 적용 페이지
      </div>
    </div>
  )
}

받아온 데이터를 {popular, playing}에 넣어서 props로 넘겨주고 데이터를 보여주기위해서 InfiniteDataList페이지로 넘겨준다

3. 데이터 리스트 무한스크롤 적용 + 스크롤 값 저장

//InfiniteDataList.js

const fetcher = async (url) =>{
    const {results} = await (await fetch(url)).json();
    return results
} //useSWRInfinite에서 요청한 url의 결과값을 리턴

const InfiniteDataList = ({queryKey,initialData})=>{

    const {
        data,  // 데이터 리스트
        mutate, // 데이터 받고 난 이후의 행동들
        size, //page의 수
        setSize, 
        isValidating,  
        isLoading // 데이터 로딩 상태
      } = useSWRInfinite(
        (index) =>
          `${queryKey}${index + 1}`,
        fetcher,
        {
            fallbackData: [initialData],
            refreshInterval: 0,
            revalidateOnFocus: false,
            revalidateOnReconnect: false,
        }
      );

const movie = size <= 1 ? initialData : []?.concat(...data); 

먼저 useSWRInfinite를 이용해서 받아올 데이터들과 url의 형식을 지정해준다.

여기서는 외부 API를 불러올때 페이지에 따라서 불러오는 url의 형식이 'http://~~~~&page=1' 으로 첫 페이지가1페이지 이런식이였기때문에 Url에 +1을 한 상태( 처음의 size의 값은 0 으로 시작한다 )로 시작을 하였고 initialData( fallbackData:[initialData] 부분 )에 이전 페이지에서 SSR 방식으로 받아온 첫 데이터 리스트들을 표시를 먼저 해주었다.

이런 방식을 선택한것은 사용자에게 첫 페이지에서 보이는 데이터들을 좀 더 빠르게 로딩을 하기 위함이다.

4. IntersectionObserver를 이용하여 마지막 div에 도달했을때 다음 데이터 불러오기

//InfiniteDataList.js
    useEffect(()=>{
        if(!isLoading){
          const options = {
            root : null,
            rootMargin: '0px',
            threshold: 1.0,
          };
      
          const observer = new IntersectionObserver((entries)=>{
            if(entries[0].isIntersecting){
              setSize(size=>size+1) //useSWRInfinite의 다음 page값을 받아오기 위해 size를 1 늘려준다.
            }
          },options)
      
          if(bottomRef.current){
            observer.observe(bottomRef.current);
          }
      
          return()=>{
            if(bottomRef.current){
              observer.unobserve(bottomRef.current)
            }
          }
        }
      },[isLoading])

IntersectionObserver를 사용할 수 있다는 가정하에 useRef로 감지할 div를 지정해주고 그 div에 스크롤이 닿았을때 setSizesize1 늘려주면서 다음 페이지에 대한 데이터 리스트들을 받아오는 형식이다.

5. 페이지를 이동하였을때 마지막 스크롤 높이 저장하기

//InfiniteDataList.js

    const handleRouteChange = () =>{
        localStorage.setItem("scrollPos",window.pageYOffset);
    }
    useEffect(()=>{

    })
    useLayoutEffect(()=>{
        window.scrollTo(0,localStorage.getItem("scrollPos"))
        window.addEventListener('beforeunload',()=>{
            localStorage.setItem("scrollPos",0);
        })
    },[])

//return 부분           
  {movie?.map((element,idx)=>{
                return  <Link href='/first' key={element?.id} onClick={handleRouteChange}>
                        <div className="movie">
                        <img src={`https://image.tmdb.org/t/p/w500/${element?.poster_path}`} />
                        <h4>{element?.original_title}</h4>
                        </div>
                        </Link>
            })}

먼저 데이터들을 뿌려주는 부분에 Link태그를 이용하여 클릭시 페이지 이동을 하도록 만들어주고 onClick이벤트로 handleRouteChange이벤트를 걸어주고 이 이벤트는 url이 이동됐을때를 감지할 수 있다. 이벤트가 발생하면 localStoragescrollPos를 키값으로 하는 마지막 스크롤 위치의 값을 저장해둔다.

이후에 페이지가 다시 로드되면 useLayoutEffect로 레이아웃이 만들어질때 localStorage에 있는 scrollPos값으로 스크롤값을 이동하게 한다.

하지만 사용자가 이 페이지를 완전히 떠날때는 스크롤 값을 0으로 바꿔서 맨 위를 바라보게 해야하기때문에 beforeunload 이벤트를 이용하여 localStoragescrollPos값을 0 으로 되돌려 준다.

0개의 댓글