인프런 커뮤니티 질문&답변

uo f님의 프로필 이미지

작성한 질문수

Next + React Query로 SNS 서비스 만들기

tanstack-query prefetchQuery 질문

24.10.16 16:35 작성

·

87

0

안녕하세요 강의를 듣는중 추가적으로 tanstack-query를 공부하다가 혼자서 도저히 이해를 할수 없는 부분이 있어서 이 부분에 대해 혹시 조언을 받을수 있을까 싶어 문의드립니다. prefetchQuery가 개인적으로 잘 이해가 안되어서 별도로 프로젝트를 생성하여 기본적인 것부터 다시 공부하고 있었습니다만, tanstack-query 공식사이트에서 권장하던 방법대로 임의적으로 코드를 생성하였더니 router.push()로 다른 페이지에 갔다가(->홈으로[/]) 다시 돌아오는것(->Post페이지(/post))을 반복하다보면 가끔 서버 컴포넌트에 있는 prefetchQuery안의 fetch와 클라이언트 컴포넌트에 있는 useQuery의 fetch가 동시에 실행이 되는 일이 가끔 발생을 해서요. fetch가 이중으로 실행이 되고 있는것 같은데 아무리 코드를 살펴봐도 제가 잘못한 부분을 찾을수가 없어서 조언을 구합니다ㅠ

 

page.tsx

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
import Post from './_component/Post';
import getPostRecommends from './_hook/fetch';

export default async function tanstackQuery() {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery({
    queryKey: ['movies'],
    queryFn: getPostRecommends,
  });

  const dehydratedState = dehydrate(queryClient);

  return (
    <HydrationBoundary state={dehydratedState}>
      <Post />
    </HydrationBoundary>
  );
}

 

post.tsx

'use client';

import { useQuery } from '@tanstack/react-query';
import getPostRecommends from '../_hook/fetch';
import { useRouter } from 'next/navigation';

export default function TanstackQuery() {
  const { data } = useQuery({
    queryKey: ['movies'],
    queryFn: getPostRecommends,
  });
  const router = useRouter();

  type PostItem = {
    id: number;
    title: string;
  };

  return (
    <div>
      <button onClick={() => router.push('/')}>홈으로</button>
      {data?.map((item: PostItem) => {
        return (
          <div key={item.id}>
            <h2>{item.title}</h2>
          </div>
        );
      })}
      {data?.message}
    </div>
  );
}

getPostRecommends

export default async function getPostRecommends() {
  if (typeof window === 'undefined') {
    console.log('서버에서 fetch 실행' + new Date());
  } else {
    console.log('클라이언트에서 fetch 실행' + new Date());
  }

  const response = await fetch('https://jsonplaceholder.typicode.com/posts?_page=1&_limit=10', {
    cache: 'no-store',
  });

  if (!response.ok) {
    throw new Error('Failed to fetch data');
  }

  const res = await response.json();

  return res;
}

기본 provider 설정

'use client';

import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode } from 'react';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 6 * 1000,
      },
    },
  });
}

type Props = { children: ReactNode };

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
  if (isServer) {
    return makeQueryClient();
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

export default function Providers({ children }: Props) {
  const queryClient = getQueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  );
}

 

서버 콘솔

401DC93D-1460-4220-A455-817EA95D91B9_4_5005_c.jpeg

브라우저 콘솔
DA8D7A9E-49FC-4F72-92D6-B347486BE4A4_4_5005_c.jpeg

 

위에 캡쳐화면 같이 fetch가 거의 동시간에 발생을 하고 있는 모습입니다. next.js router cache 때문에 30초마다 서버 컴포넌트쪽이 리랜더링 되어서 페이지를 새로고침을 하지 않고 router.push로 다시 페이지에 들어가도 서버 컴포넌트쪽이 다시 실행된다는건 이해를 했는데, 그렇다면 초기 랜더링할때와 똑같이 데이터가 prefetch되어서 클라이언트쪽 useQuery가 실행이 되지 않아야하지 않나요? 왜 두번이나 fetch가 도는건지 아무리 자료를 찾아봐도 잘 모르겠어서 결국 문의드리게 되었습니다ㅠ

답변 2

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

2024. 10. 16. 16:49

데브툴을 한 번 보는게 좋을 것 같읒데요. 서버로부터 온 데이터를 받았을 때 데이터가 fresh 상태여야 새로 요청을 하지 않습니다.

uo f님의 프로필 이미지
uo f
질문자

2024. 10. 16. 17:04

뭔가 모종의 이유로 서버에서 prefetch가 됐음에도 데이터가 fresh한 상태로 업데이트가 안되고 있는것 같단 말씀으로 이해했습니다! 다만 거의 동시간에 일어나서 데브툴을 열어서 확인을 해도 제가 서버로부터 온 데이터를 받았을때 데이터가 fresh 상태인지는 확인할수가 없었습니다!ㅠ 업데이트되어 fresh한 상태로만 확인되고 있습니다. 일단 post.tsx에서

const query = queryClient.getQueryState(['movies']);

추가하고 콘솔로 찍어본 결과 이중 패치때에는 이렇게 출력이 되고 있습니다..!

AD21EB0F-F53B-4AAD-A325-86E18253E8A7.jpeg

 dataUpdatedCount가 초기화 된것을 보면 모종의 이유로 캐시가 아예 사라진것 같습니다...

+) 추가로 isStale도 확인해보니 두번 패치될때는 해당 값이 true로 나오고 있습니다. 왜 변경이 안되는지는 모르겠지만 말씀하신대로 서버로부터 온 데이터를 받았을 때 데이터가 stale 상태라서 두번 fetch가 되고 있는것 같습니다...이해를 할수가 없네요...

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

2024. 10. 16. 18:38

아, 이제 문제 상황을 이해했는데요. 일단 처음 렌더링 외에는 prefetchQuery와 useQuery 둘 다 실행될 수 있습니다. hydration은 페이지에 주소를 쳐서 접근할 때에만 일어나고 페이지를 이동할 때는 발생하지 않습니다. next.js는 첫 페이지만 ssr이고 그 다음 페이지부터는 csr이기 때문입니다.

uo f님의 프로필 이미지
uo f
질문자

2024. 10. 16. 23:30

답변 감사합니다..! 죄송하지만 추가적으로 공부를 한뒤에도 제가 말씀하신 바를 제대로 이해를 하지 못해 다시 여쭙습니다ㅠ hydration이 발생하지 않는다는 말씀이, router cache의 유효기간이 지나(약 30초) 서버에 재요청이 들어가게 되면서 prefetchQuery가 실행되어도 첫 랜더링 이후에는 hydration이 발생되지 않아 useQuery도 같이 실행될수 있다는 말씀이실까요? 만약 그렇다면 tanstack-query에서 prefetchQuery를 사용하는 이상 이중 fetch문제는 본질적으로 따라오게 되는걸까요..?ㅠ

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

2024. 10. 16. 23:40

네 맞습니다. 이중 fetch 문제를 해결하려면 서버컴포넌트 자체에서 api 요청을 한 뒤에 props로 넘겨주는 게 낫습니다. 다만 이중 fetch 자체는 보통 큰 문제는 아니긴 합니다.

uo f님의 프로필 이미지
uo f
질문자

2024. 10. 17. 00:10

넵 이해했습니다ㅠ 늦은시간까지 정말 감사했습니다 제로초님!

0

인프런 AI 인턴님의 프로필 이미지

2024. 10. 16. 16:42

안녕하세요, 인프런 AI 인턴입니다.
uo f님의 질문에 대해 React Query와 Next.js를 사용하는 과정에서 발생하는 prefetchQuery와 useQuery의 이중 요청 문제에 대해 설명드리겠습니다.

이중 Fetch 요청 문제

  1. 서버 컴포넌트와 클라이언트 컴포넌트 동작 차이: Next.js에서 서버 컴포넌트의 prefetchQuery는 서버 사이드에서 데이터를 가져와 React Query의 쿼리 캐시에 저장합니다. 이 과정은 클라이언트 컴포넌트가 마운트될 때 중첩된 useQuery를 통해 해당 데이터를 재사용할 수 있도록 하는 시스템입니다. 하지만 경우에 따라 클라이언트에서 prefetch된 데이터가 유효하지 않거나 새 데이터를 필요로 할 때 useQuery가 다시 서버에 fetch 요청을 보낼 수 있습니다. 이러한 이유로 클라이언트에서 다시 요청이 발생할 수 있습니다.

  2. 데이터 유효성(stale) 관리 문제: React Query는 데이터의 유효성을 관리하기 위한 여러 설정(staleTime, cacheTime 등)을 제공합니다. 이 설정이 초기화되거나 캐시된 데이터가 구식으로 간주되면 클라이언트 컴포넌트가 처음 마운트될 때에도 새롭게 데이터를 가져올 수 있습니다.

해결 방안

  • 데이터의 Stale 상태와 Cache 전략 조정: staleTimecacheTime을 조정하여 클라이언트에서 데이터를 더 자주 재요청하지 않도록 설정할 수 있습니다. 적절한 값으로 설정하여 클라이언트에서 불필요하게 서버에 추가 요청을 하지 않도록 최적화합니다.

  • prefetchQuery 활용법 점검: 서버에서 데이터를 가져올 때 prefetchQuery를 잘 활용한 후, 클라이언트에서 동일한 queryKey를 사용하여 데이터를 요청할 때, useQuery의 옵션 중 enabled 값을 잘 설정해 필요 이상으로 요청이 중복되지 않도록 조정해 보세요.

기존에 비슷한 맥락에서 해결 방안을 제공한 글을 참고하시면, 상황 이해와 문제 해결에 도움이 되실 수 있습니다:

  • useQuery vs useQueryClient: 이 질문에서는 prefetch된 데이터를 QueryClient나 useQuery를 통해 어떻게 재사용할지, 페칭 전략의 차이가 무엇인지 설명하고 있습니다.

  • react-query 캐싱 및 데이터 스트레이트지: 이 글에서는 prefetch, useQuery, 캐싱 및 페칭 전략을 조정하는 방법에 대해 설명하고 있어, 중복 요청 문제를 이해하고 개선하는 데 도움이 될 것입니다.

위의 내용을 참고하여 문제를 해결해 보시길 바라고, 추가적으로 더 궁금한 사항이 있으면 이어서 질문해 주세요.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

uo f님의 프로필 이미지

작성한 질문수

질문하기