😋왜 필요할까?
react-query가 제공해주는 SSR 환경에서 사용법을 보면서 한가지 의문이 들었습니다.
react query가 제공해주는 캐싱, 리페칭 등등의 기능들이 매력적이긴한데...
사실 대부분의 기능은 캐싱 , revalidate 같은 부분은
next.js가 제공해주는 확장된 fetch 함수로도 충분하지 않나??
그렇다면 next.js에서 react-query를 꽤나 복잡한 설정을 꾸역꾸역 설정하며 굳이굳이 사용할 필요성이 있을까?
라는 의문이 들었습니다.
물론 아직 제공되지 않는 부분도 많을 것 같습니다.
하지만 꼭 필요할까?라고 묻는다면 좀 애매할 것 같았습니다.
그러던 중 You Might Not Need React Query라는 글을 발견했어요
https://tkdodo.eu/blog/you-might-not-need-react-query
이 글을 요약하면 react query는 next.js에서 필요하지 않을 수 있다.
하지만 과도기적인 현재 시점에서 react query는 통합을 위한 하나의 솔루션이 될 수 있다.
정도인 것 같습니다.
예컨대 리액트쿼리의 경우 서버컴포넌트가 아직 잘 지원하지 못하는 사용 사례를 아주 유용하게 지원해줍니다.
Google 검색 순위에 높게 위치하게되면서 많은 분들이 이 글을 읽어주셨습니다.
부끄럽게도 살아가는게 바쁘다보니 해당 글이 불러올 수 있는 오해들을 바로잡는것을 미루어왔습니다.
그래서 기존 글의 내용은 유지하면서 많은 오해를 불러오고 있는 부분들에 대한 설명을 추가하였습니다.
추가 된 내용은 별도의 표시가 존재합니다. 감사합니다.
😙React Server Component의 등장
리액트 서버 컴포넌트는 react 18에 나온 아주 강력한 기능 중 하나입니다.
자세한 내용은 react.dev 사이트에서 확인할 수 있습니다.
또한 github 페이지에서도 잘 확인할 수 있답니다!
RSC(React Server Component)는 Server Side Rendering을 상호보완해주는 기술입니다.
또한 Next.js의 서버 컴포넌트 역시 RSC에 기반하고 있습니다.
https://github.com/XionWCFM/react-article-study/blob/main/giljong/RSC.md
RSC에 대한 자세한 정보가 궁금하다면 위 링크를 참고해주세요!
서버 컴포넌트의 장점 중 하나는 data fetching 로직을 서버에 둘 수 있다는 것입니다.
그리고 이것은 지금 이야기할 react-query와도 관련이 깊어보이네요
next.js docs의 React Essentials를 참고하면 위와 같은 설명을 던져줍니다.
1. 데이터 페칭 로직을 서버로 옮길수 있고
2. DB와 더 가까이에서 뭔가를 처리할 수 있고
3. 번들사이즈가 클라이언트에 영향을 주지 않게할 수 있다.
🥰Next.js에서의 데이터페칭 솔루션
마침 번역프로젝트에서 번역한 문서가 있어 번역본을 링크합니다.
원문으로 보고 싶으신 분들의 경우
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
이 링크를 참조해주세요!
Next.js의 app router는 위에서 서술한대로 async 방식을 통해 데이터를 fetching합니다.
그리고 Next.js 측에서 권장하는 방법도 존재합니다.
그 방법은 크게 네가지로 요약할 수 있는데요 다음과 같습니다.
1. server component를 이용하여 서버측에서 데이터를 fetching 합니다.
2. data fetching은 병렬적으로 수행하는것이 좋습니다.
3. layout 및 page의 경우 데이터를 사용하는 곳에서 데이터를 가져오는것이 좋습니다.
4. Suspense, Loading UI를 이용해 점진적으로 렌더링을 수행하는것이 좋습니다.
async function getData() {
const res = await fetch('https://api.example.com/...');
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
// Recommendation: handle errors
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return <main></main>;
}
이와 같은 형식으로 data fetch를 할 수 있습니다.
export default async function Page() {
// revalidate this data every 10 seconds at most
const res = await fetch('https://...', { next: { revalidate: 10 } });
const data = res.json();
// ...
}
그리고 next가 제공하는 확장기능을 이용해 revalidate도 지정해줄 수 있어요
fetch(`https://...`, { cache: 'no-store' })
fetch('https://...', { cache: 'force-cache' })
뿐만 아니라 캐싱 기능도 지원해줍니다.
이렇듯 next.js에서 확장 fetch 함수는 기존에는 리액트 쿼리에게 일임했던
캐싱 기능 역시도 지원해주는 모습을 볼 수 있습니다.
물론 이 fetch가 리액트쿼리의 모든 부분을 대체해줄 수는 없습니다.
또한 마찬가지로 axios의 모든 부분을 대체해줄 수도 없어요
하지만 대부분의 사용례를 커버해줄 수 있을만큼의 기능은 제공해주는 것을 확인할 수 있었습니다.
😂react query
리액트 쿼리를 app router 환경에서 사용하기 위해서 공식문서에서 추천하는 방법이 있습니다.
두가지 방법이 존재하는데 initialData 속성을 이용한 방법과 Hydration을 이용한 방법이 있습니다.
공식문서를 보아도 조금 이해하기가 어려웠던 와중 너무너무 좋은 튜토리얼이 있어 가져와보았습니다.
https://codevoweb.com/setup-react-query-in-nextjs-13-app-directory/
기본적인 시작은 이 블로그에서 배우시면 될 것 같네요
그런데 그냥 데이터페칭만 하는 코드를 작성해도 꽤...복잡한 코드와 구성이 됩니다.
나는 그저 api 받아와서 화면에 뿌리고 싶을 뿐이야! 라는 분들은 그냥 fetch함수를 쓰는것이 더 좋을듯하네요
😐결론
일반적인 data fetching 솔루션이 필요할 때에 react query는 프로젝트에 필요하지 않을 수 있습니다.
하지만 data fetching과 연관된 까다로운 사용사례를 구현해야할때 react query는 매력적인 선택지가 될 수 있습니다.
특히 무한스크롤같은것을 구현하려고 할때 아주 유용히 사용할 수 있습니다.
😉 추가사항
위 본문에서 다루는 내용은 정적으로 데이터를 페칭하는 작업만 수행하는 경우
next.js의 pages router 를 떠올렸을때에는 getServerSideProps 등의 함수에서 수행하던 작업의 이야기입니다.
당연하게도 여기에 현대 애플리케이션을 구성하는 기본적인 요구사항들을 충족시키기 위해서는 부족함이 많습니다.
또한 위 본문에서 다루는 reactquery는 서버사이드에서의 리액트쿼리를 다룹니다.
따라서 기존 클라이언트 사이드에서 사용하던 리액트쿼리의 입지를 next.js의 fetch가 대체하는것은 불가능합니다.
이 글은 리액트쿼리를 서버사이드에서 next.js의 fetch 대신 사용하는것을 다루는 것입니다.
이 점 유의하면서 읽어주시면 감사하겠습니다.
여기부터는 추가사항입니다.
대표적인 오해 1 : Next.js에서도 fetch 쓰면 캐싱 지원되는데 리액트 쿼리 캐싱 필요 없는거아니야??
next.js와 reactquery 모두 캐싱이라는 워딩을 사용하다보니 가장 먼저 생기는 오해인 것 같습니다.
결론부터 이야기하면 두 라이브러리 | 프레임워크 모두 캐싱을 하는 것은 맞지만 캐싱의 목적은 상이합니다.
@tanstack/react-query의 등장은 next.js의 서버컴포넌트보다 빨랐습니다.
두 라이브러리 | 프레임워크가 캐싱을 하는 이유와 제공하는 기능을 살펴보면 이해가 쉽습니다.
@tanstack/react-query | next.js servercomponent fetch |
1. 클라이언트 사이드에서 일어나는 다중페칭 문제를 효율화하기 위해 2. 단일출처에서 상태를 관리하는걸 통해 SSOT 를 보장 |
1. 서로 자원 공유가 어려운 서버컴포넌트의 특성상 일어나는 다중페칭 문제를 해결하기 위해 ex: 100개의 서버컴포넌트가 동일 엔드포인트에 동일한 요청을 날린다면..? |
멀리서 바라보면 다중페칭 문제를 해결한다는 점은 같지만
next.js의 fetch caching은 서버컴포넌트의 문제를 해결하는데에 초점이 있습니다.
관리 방식에서도 차이가 있습니다.
@tanstack/react-query | next.js servercomponent fetch |
1. 쿼리키를 매개로 각 fetch 상태를 관리한다 | 1. 엔드포인트와 옵션을 기준으로 fetch 상태를 관리한다. |
https://nextjs.org/docs/app/building-your-application/caching
next.js 의 caching에 대한 문서를 살펴보면 next.js가 캐싱을 하는 방법에 대하여 자세히 설명이 된 것을 확인할 수 있습니다.
next.js는 최적화를 위해 다양한 항목들을 캐싱하는데요 RSC의 Payload , Request에 대한 요청 항목 | 결과 등
다음 사진에서 간략하게 각 캐싱이 이루어지는 부분들을 확인할 수 있습니다.
fetch에 대한 캐싱 부분은 다음 항목을 보면 이해가 쉽습니다.
앞서 간략히 설명한 것처럼 next.js의 서버컴포넌트는 그 특성상 동일한 리퀘스트를 여러 서버컴포넌트에서 요청할 확률이 높다.
라는 태생적인 문제를 가지게 됩니다. 따라서 이를 효율적으로 관리하기 위한 별도의 캐싱스토어가 필요하게 되는것입니다.
그리고 그 방식은 "동일한 엔드포인트와 동일한 옵션을 가진 요청이라면 같은 요청이며 같은 결과가 올것이다" 라는 근거에 의존합니다.
이것을 한단어로 줄여서 이야기하면 엔드포인트가 "멱등성"을 지닐것이라고 가정한다는 것입니다.
따라서 이 방법을 통해 동일한 리퀘스트들을 식별할 수 있다. 라는 근거를 가지고 동일한 요청에 대하여 동일한 응답을 내려주면 되는 것이죠
자세한 사항은 next.js caching 문서에서 더욱 잘 설명하고 있으니 넘어가도록 하겠습니다.
반면 @tanstack/react-query의 경우는 동일 endpoint이냐 동일 option이냐는 전혀 신경을 쓰지 않습니다.
그저 쿼리키를 기준으로 해당 쿼리키에 대한 요청사항을 처리할 뿐입니다.
물론 쿼리키를 활용하여 쿼리키에 엔드포인트와 옵션을 포함시키도록한다면
위에서 본 fetch 캐싱에 대한 멘탈모델을 흉내낼 수는 있습니다.
또 캐싱스토어에 접근하는 방법도 다릅니다. fetch는 캐싱스토어에 접근하기위해서 (실제로 네트워크에 영향을 안준다는것을 알아도)매번 fetch를 수행해야하지만
리액트쿼리는 내가 원하는 값이 담긴 쿼리키의 값만 알고있다면 리액트 쿼리의 스토어에서 해당 쿼리키의 값을 빼오는 행위가 가능하다는 것이죠
이처럼 next.js가 제공하는 fetch 캐싱과 리액트쿼리의 캐싱은 서로 관리하고자하는 영역도 다르며 해결하고자하는 문제도 다릅니다.
대표적인 오해 2 : 굳이 ServerComponent에서 리액트 쿼리를 뭐하러 써??
클라이언트측의 요구사항으로 인해 리액트쿼리의 기능이 필요한 경우 리액트쿼리의 스토어에 상태가 있어줘야
적절히 그 상태를 요리할 수 있지만 서버측 페칭에 리액트쿼리가 개입하지 못하게되면
페칭을 서버에서도하고 리액트쿼리를 위해서 클라이언트에서도 해야하는 비효율이 발생하게됩니다.
이를 해결하기 위해서 리액트쿼리와 서버컴포넌트를 통합할 때 위에서 잠깐 다룬 initialData, Heydration 방식을 채택하는 것입니다.
이것을 통하여 서버컴포넌트에서 가져온 데이터를 적절하게 클라이언트에서도 사용할 수 있게하는것이죠
이러한 작업을 통해서 서버에서 클라이언트로 페칭데이터를 넘겨주는 것이 가능해지니
서버에서 가져왔던걸 클라이언트에서 또 가져오는 비효율을 막을 수 있어집니다.
하지만 이 부분은 필수적인 영역은 아닙니다. 만약 어느정도의 성능 손해가 있어도 크게 상관이 없는 경우
빠르게 개발해야하는 경우 , 리액트쿼리와 서버컴포넌트에 대한 맥락을 잘 모르는 경우와 같은 상황에 제가 놓였다면
저는 가장 먼저 서버컴포넌트 - 클라이언트컴포넌트간의 데이터 공유를 위한 세팅을 건너뛸것입니다.
이러한 경우에는 그냥 use client 지시문만 붙여서 react에서 쓰던것과 동일하게 쿼리프로바이더 만들고
루트에 프로바이더 씌워서 클라이언트측에서만 리액트쿼리를 쓰셔도 전혀 문제가 없습니다.
나중에 천천히 마이그레이션 해도 전혀 늦지 않을 것입니다.
대표적인 오해 3 : 이 글은 왜 앞에서 한말이랑 뒤에서 한말이 달라지나요??
이해가 설익은 상태로 썼던 부족한 글을 많은 분들이 읽어주셨습니다.
아무도 안 읽는 글이었다면 그냥 신경끄고 살았을 것 같은데 저도 책임감이라는게 생기더라구요
잘못된 내용을 바로잡는것도 바로잡는것이지만 기존 글의 내용을 원하시는 분도 있을 것 같아 기존 글도 남겨두느라 발생한 현상입니다.
읽어주셔서 감사합니다.
😕레퍼런스
https://tech.kakaopay.com/post/react-server-components/
https://patterns-dev-kr.github.io/rendering-patterns/react-server-components/
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
'Next.js' 카테고리의 다른 글
next.js blog를 contentlayer에서 next-mdx-remote 로 전환한 후기 (1) | 2024.04.13 |
---|---|
next.js13 app dir mui tailwindcss starter kit 보일러 플레이트 (0) | 2023.08.18 |
next-auth를 이용해 Google 로그인 구현하기(기초) (1) | 2023.05.31 |
Next.js에서 에러를 핸들링 하는 방법 (0) | 2023.05.27 |
Next.js에서 환경변수 .env를 클라이언트 사이드에서도 사용하려면..? (0) | 2023.05.05 |