Next.js에서 왜 react-query를 쓸까?

쭌로그·2025년 2월 13일
2

Tanstack query

서론

Next.js는 React 진영에서 가장 많이 사용하는 SSR 프레임워크입니다. CSR에서는 어려운 SEO 최적화에 많이 유리해 Next.js를 사용합니다. 또한 SSR만 사용되는 것이 아닌 CSR, SSG 등 다양한 방식의 렌더링을 통해 성능 최적화ㅣ가 가능하기 때문에 많이 사용되고 있습니다.

사내 프로젝트는 Vue3 프로젝트이지만 Tanstack-query를 사용했고 Server State를 저장하고 있다는 점 자체가 큰 장점으로 다가왔습니다. 사내 프로젝트는 백오피스 화면이기 때문에 데이터의 양 자체가 많고, 페이지네이션, 로그 데이터 등 상태를 저장함에 있어 장점이 있는 화면이 많았기 때문입니다.

그 당시에 혼자 Next.js도 공부하고 있었고 자연스럽게 "Next.js는 fetch 함수를 통해 데이터 fetching을 진행하는데 caching, refetching을 제공한다... 그럼 왜 Tanstack-query를 사용할 필요가 없는거 아닐까..?" 라는 의문이 들었고 Next.js에서는 그럼에도 왜 Tanstack query를 사용하는지 이유를 알아보고 정리하려고합니다.

Next.js에서 Data fetching

Next.js에서 제공하는 fetch 함수는 다양한 옵션을 caching과 revalidate를 제공합니다.

async function getData() {
  const res = await fetch('https://api.example.com/...')
 
  if (!res.ok) {
    throw new Error('Failed to fetch data')
  }
 
  return res.json()
}
 
export default async function Page() {
  const data = await getData()
 
  return <main></main>
}

1. caching

//cache "force-cache | "no-store"
const res = await fetch('https://api.example.com/...',{cache: "force-cache"});
  • force-cache : 데이터 캐시에서 일치하는 값을 찾고(hit) 없다면 api 호출을 진행하고 캐시에 저장합니다.(set)
  • no-store : 캐시를 사용하지 않습니다. 또한 api 호출을 진행한 후에도 캐시를 저장하지 않습니다.

2. revalidate

// revalidate  false | 0 | number
const res = await fetch('https://api.example.com/...',{revalidate: 3});
  • false : 리소스를 무기한 캐시합니다.
  • 0 : 캐시를 진행하지 않습니다.
  • number : number초 만큼 리소스를 캐싱하고 있습니다.

이렇게 좋은 기능을 주는데... react-query를 왜 쓰나..?

실제로 단순한 api 호출, 기능이 단순한 웹 페이지라면 굳이 react-query를 사용할 이유가 없습니다. 앞서 말했듯이 이미 fetch 메서드에서 모든 기능을 제공하기 때문입니다.
하지만 react-query를 사용해야 좋을 상황도 있습니다.

  • 무한스크롤, 낙관적 업데이트가 필요한 서비스
  • fallback UI, Skeleton UI 등이 필요한 경우
  • 개인 Get 요청이 필요한 경우

1. 무한스크롤

무한 스크롤과 낙관적 업데이트는 react-query를 사용한다면 간단하게 적용할 수 있는 기술들입니다. react-query를 사용하지 않아도 구현할 수 있지만 사용한다면 더 간단하게 구현할 수 있습니다.

무한 스크롤은 react-query에서 제공하는 infiniteQuery와 intersection Observer api를 사용하면 간단하게 구현할 수 있습니다.

낙관적 업데이트도 react-query에서 제공하는 getQueryData, setQueryData와 cancelQueries를 사용하면 유저에게 더 좋은 사용자 경험성을 제공할 수 있습니다.

useInfiniteQuery({
      queryKey: [category, searchDate, searchRegions, searchTags],
      queryFn: ({ pageParam }) => getCategoryList(pageParam),
      initialPageParam: 0,
      getNextPageParam: (lastPage) => lastPage?.nextCursor,
});

2. fallback UI, Skeleton UI 등이 필요한 경우

기존에 fetch 메서드를 사용할 때는 isLoading, data, error등 각 state를 직접적으로 선언해주거나 hooks로 만들어야 했습니다. 하지만 react-query는 isLoading, data, error를 제공하기 때문에 반복적인 작업 없이 코드를 작성할 수 있습니다.

import { useQuery } from '@tanstack/react-query'

export default function DelayedData() {
  const { data } = useQuery({
    queryKey: ['delay'],
    queryFn: async () => (await fetch('https://api.heropy.dev/v0/delay?t=1000')).json()
  })
  return <div>{JSON.stringify(data)}</div>
}

3. 개인 Get 요청

저는 이 점이 가장 와닿았습니다. Next.js는 SSR입니다. SSR이란 말은 하나의 서버에서 여러 요청을 처리한다는 점입니다. 만약 A유저와 B유저가 동일한 서비스에서 다른 결과를 호출해야한다면 캐싱이 좋은걸까요? 결과는 다른 값이 반환 되어야 하지만 Next.js에서 revalidate 설정을 했다면 정해진 시간동안 같은 결과가 발생하게 됩니다.

no-store를 사용할 수는 있지만 이는 api 호출이 증가하여 성능이 저하될 수 있습니다.
이러한 문제는 react-query에서 해결할 수 있습니다. 각 유저마다 고유한 key를 통해 다른 정보를 받아올 수 있기 때문에 추가 비용이 발생하지 않습니다.

react-query와 fetch의 캐싱
fetch와 react-query 모두 caching을 지원합니다. 하지만 이 캐싱이 같은 캐싱은 아닙니다.
Tanstack-query는 클라이언트 사이드에서 일어나는 데이터 fetching 문제를 해결하기 위해서라면, fetch 함수는 데이터 공유가 어려운 서버컴포넌트의 특성에서 오는 데이터 fetching 문제를 해결하기 위함이기 때문입니다.

fetchtanstack query
목적공유가 어려운 서버 컴포넌트 사이에서 데이터 공유를 위해CSR 환경에서 데이터 fetching 과정에서 발생하는 비용 절감을 위해
방식옵션을 기준으로 관리query key 값을 통한 관리

마치며

비용 절감은 중요합니다. 다양한 성능 개선을 통해 사용자 UX를 개선하고 속도를 개선하는데 가장 핵심적인 역할을 하기 때문입니다. 하지만 프로젝트의 규모, 서비스의 환경에 따라 비용 절감의 방식이 달라질 수 있다고 생각합니다. 어떤 경우는 번들 사이즈, 어떤 경우는 API 호출을 더 중점적으로 개선해야합니다.

이 기술이 좋다고 말한다는 이유만으로 무작정 기술을 사용하는것은 바람직하지 못하다고 생각합니다. 오히려 이 기술이 왜 좋은지, 어떤 환경에서 좋은지를 알아본 다음, 그 기술을 사용했을 때 사용한 효과가 드러난다고 생각합니다.

이번 공부를 통해 무작정 기술을 사용하지 말자라고 다짐을 했던 학습이였습니다. 항상 "왜?"라는 생각을 가지고 개발을 하자라는 생각을 다잡을 수 있었던 시간이였던 것 같습니다.

profile
매일 발전하는 프론트엔드 개발자

0개의 댓글