

Next.js에서 SSR + SSG로 외부 API로 데이터를 받아온 후 로드를 한 이후에 IntersectionObserver로 div를 감지한 후 무한스크롤을 적용시키고 받아온 데이터를 클릭했을떄 세부 페이지로 이동 후 뒤로갔을때 마지막으로 스크롤 한 위치로 이동하기까지의 과정이다.
//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에서 무료로 제공해주는 데이터를 이용하였다.
SSR과SSG를 나누는 요소는 받아온 데이터를 이후에 어떻게 사용할지에 따라서 나뉜다. 만약CRUD형식으로 받아온 데이터의 변화가 일어난다면SSR(getServerSIdeProps)로 데이터를 받아와 뿌려준후 변경사항에 대해서 처리를 해주고 한번만 받아와서 데이터를 사용한다면SSG(getStaticProps)를 이용하여 데이터를 뿌려준다.
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페이지로 넘겨준다
//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방식으로 받아온 첫 데이터 리스트들을 표시를 먼저 해주었다.
이런 방식을 선택한것은 사용자에게 첫 페이지에서 보이는 데이터들을 좀 더 빠르게 로딩을 하기 위함이다.
//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에 스크롤이 닿았을때setSize로size를1늘려주면서 다음 페이지에 대한 데이터 리스트들을 받아오는 형식이다.
//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이 이동됐을때를 감지할 수 있다. 이벤트가 발생하면localStorage에scrollPos를 키값으로 하는 마지막 스크롤 위치의 값을 저장해둔다.
이후에 페이지가 다시 로드되면
useLayoutEffect로 레이아웃이 만들어질때localStorage에 있는scrollPos값으로 스크롤값을 이동하게 한다.
하지만 사용자가 이 페이지를 완전히 떠날때는 스크롤 값을
0으로 바꿔서 맨 위를 바라보게 해야하기때문에beforeunload이벤트를 이용하여localStorage의scrollPos값을0으로 되돌려 준다.