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
으로 되돌려 준다.