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

홍홍님의 프로필 이미지
홍홍

작성한 질문수

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

클라이언트 react-query

prefetchQuery 적용 후 Warning: Text content did not match. 오류가 발생합니다.

해결된 질문

작성

·

328

0

안녕하세요. 강의 잘 보고 있습니다.!!👍👍

react-query를 적용 중 오류가 표시되어 오류와 궁금한 부분이 있어서 문의 드립니다.


Text content did not match. 오류가 발생하는데 어느 부분이 문제인지 잘 모르겠습니다 ㅠㅠ

prefetchQuery 를 사용하여 서버에서 post(트윗)데이터를 프리패치 후 TweetList컴포넌트에서 useQuery를 사용하게 만들었습니다. Post의 데이터는 msw에서 알려주신 faker를 통해서 요청마다 content를 동적으로 생성하게 했구요.
콘솔로 출력한 데이터와 reactQuery개발 도구의 데이터는 동일한데 화면에 표기된 데이터는 전혀 다른 데이터로 표시되네요..


그리고 방식이 prefetchQuery 를 사용한 데이터는 staleTime과 상관없이 Fresh상태로 되어 useQuery 호출 시 데이터가 아직 fresh한 상태이므로 서버를 호출하지 않고 캐쉬에서 가져와 보여주는 방식이 맞나요?


오류로그
app-index.js:33 Warning: Text content did not match. Server: "Tener adulatio decens conitor. Thesis apostolus ago at decerno. Iste uberrime commodo. Verus amitto quas cometes delicate. Cognomen alii curto ciminatio. Solitudo complectus tristis. Teneo sum vindico. Adhuc decimus triumphus ipsa arguo umquam addo adulatio cresco. Sponte degenero non trado cauda beneficium laboriosam approbo." Client: "Tremo collum sint terror templum summisse viduo usque unus benevolentia. Eligendi substantia concido amissio uredo turpis aperte. Admoneo titulus audeo conicio fuga." at div at div at div at article at TweetWrapper (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/TweetWrapper.tsx:11:11) at Tweet (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/Tweet.tsx:31:11) at TweetList (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/TweetList.tsx:16:93) at HydrationBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@tanstack+react-query@5.39.0_react@18.2.0/node_modules/@tanstack/react-query/build/modern/HydrationBoundary.js:14:11) at HomeContextProvider (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/Provider.tsx:17:11) at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:242:11) at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:74:9) at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:82:11) at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:84:11) at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:340:11) at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:162:11) at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:152:9) at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:227:11) at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/render-from-template-context.js:16:44) at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:359:11) at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:242:11) at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:74:9) at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:82:11) at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:84:11) at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:340:11) at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:162:11) at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:152:9) at ScrollAndFocu


소스코드

(Tweet이 Post입니다! 나중에 바꿔야겠네여)
[page.tsx] /home

import React from "react";

import HomeTopTab from "./_components/HomeTopTab";
import WriteForm from "./_components/WriteForm";
import TweetList from "./_components/TweetList";
import HomeContextProvider from "./_components/Provider";
import {
  QueryClient,
  dehydrate,
  HydrationBoundary,
} from "@tanstack/react-query";
import getPostRecommends from "./_lib/getPostRecommends";

const HomePage = async () => {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery({
    queryKey: ["tweet", "recommends"],
    queryFn: getPostRecommends,
  });
  const dehydratedState = dehydrate(queryClient);

  return (
    <HomeContextProvider>
      <HomeTopTab />
      <WriteForm />
      <HydrationBoundary state={dehydratedState}>
        <TweetList />
      </HydrationBoundary>
    </HomeContextProvider>
  );
};

export default HomePage;

 

TweetList.tsx

"use client";

import React from "react";
import Tweet from "./Tweet";
import getPostRecommends from "../_lib/getPostRecommends";
import { Post } from "./TweetWrapper";
import { QueryClient, useQuery } from "@tanstack/react-query";

const TweetList = () => {
  const { data: tweets } = useQuery<Post[]>({
    queryKey: ["tweet", "recommends"],
    queryFn: getPostRecommends,
    enabled: false,
  });
  console.log("🚀 _ TweetList _ tweets:", tweets);
  return tweets?.map((tweet) => <Tweet post={tweet} key={tweet.postId} />);
};

export default TweetList;

 

 


답변 2

0

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

faker를 사용한 경우 저 유형의 에러가 많이 발생합니다. 매번 실행할 때마다 다른 결과를 리턴하므로 서버에서 렌더링될 때와 클라이언트에서 렌더링될 때 데이터가 다르기 때문입니다.

prefetchQuery도 staleTime의 적용을 받습니다. staleTime 설정에 따라 stale/fresh 여부가 결정됩니다.

https://tanstack.com/query/latest/docs/framework/react/guides/prefetching#prefetchquery--prefetchinfinitequery

홍홍님의 프로필 이미지
홍홍
질문자

빠른 답변 감사합니다.

staleTime를 1초로 설정해도 reactquery 개발툴에 fresh로 표시가 되네요.

우선은 정적 데이터로 오류부터 확인해보고 다시 진행해야겠네요.!

좋은 저녁 보내세요😎

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

데브툴에 fresh 시간이 언제까지로 뜨나요??

홍홍님의 프로필 이미지
홍홍
질문자

 image

말씀하신 fresh 시간을 확인하는 부분이 LastUpdated부분이 맞을까요?

테스트를 위해 설정한 staleTime 은 1이고 1분이 넘게 지나도 fresh로 표시가 되네요.

그리고 처음 질문드린 에러 Text content did not match 가 이해가 안되네요...faker 사용이 문제라고 답변을 주셨는데 첨부한 이미지의 오른쪽을 보시면 작성한 msw의 handlres 콘솔로그를 보시면 한번 만 호출 되는 것 같습니다. 호출이 한번인데 어떻게 다른 데이터가 존재하는지 잘 이해가 안되네요...

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

data explorer 더 밑에 메타데이터들 나올겁니다!

지금 faker 코드가 msw 코드에서만 있는게 맞나요? 혹시 브라우저 콘솔에는 안 찍히나요?

홍홍님의 프로필 이미지
홍홍
질문자

헛 강사님의 말씀을 듣고 faker를 사용하는 부분을 확인해 보니 제가 Post컴포넌트 내부의 faker 사용한 부분을 제거하지 않았네요😥
사용하는 부분을 제거 후 오류도 사라지고 react-querystaleTime도 정상적으로 동작해서 fresh와 stale가 바뀌네요!
오류가 진짜 말그대로 서버에서 받은 부분과 클라이언트에서 faker로 다시 한번 데이터를 만들어서 html이 맞지 않아 발생한 것 같아요!
늦은시간까지 빠른 답변 정말 너무너무 감사합니다.

오늘도 좋은 하루 보내세요!👍👍👍👍

 

 

0

안녕하세요, 인프런 AI 인턴입니다.

홍홍님의 질문에 대한 내용을 검토해 보았으며, “Text content did not match. 오류가 발생하는 상황과 prefetchQuery 사용으로 인한 데이터 캐싱과 관련된 문제에 대해 답변드리겠습니다.

먼저, “Text content did not match.” 오류는 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR) 사이에 렌더링된 내용이 불일치할 때 발생합니다. 일반적인 원인 중 하나는 데이터 생성 시점의 차이로 인해 초기 렌더링 시 서버와 클라이언트가 다른 데이터를 사용하여 렌더링하는 경우입니다. 홍홍님의 경우 mswfaker를 사용하여 동적으로 데이터를 생성하고 있으며, 이로 인해 발생하는 것으로 보입니다.

prefetchQuery 사용 시 데이터가 캐시되고, 이 캐시된 데이터는 staleTime에 따라 관리됩니다. 만약 prefetchQuery를 통해 데이터를 미리 가져온 경우, 해당 데이터는 일정 시간 동안 fresh 상태로 유지되며, 이 기간 동안에는 useQuery 호출 시 새로운 데이터를 서버에 요청하지 않고 캐시에서 바로 가져와 사용합니다. staleTime이 지나기 전의 useQuery 호출은 서버 호출이 이루어지지 않고 캐시된 데이터를 사용하여 처리되는 것이 맞습니다.

데이터 캐싱과 관련된 문제에 대해 비슷한 질문과 답변을 참고해 보시면 도움이 될 수 있습니다:

  • useQuery 데이터 캐싱 설정”: 이 글에서는 useQuery를 사용할 때 데이터 캐싱 설정에 대해 설명하고 있습니다. staleTime 설정을 통해 얼마나 오랫동안 데이터를 fresh 상태로 유지할지 설정하는 방법에 대해 다루고 있습니다.

  • 리액트쿼리로 변경 후 질문있습니다.”: 특히, 이 질문에서는 리액트 쿼리를 적용한 후의 행동과 서버에서 클라이언트 사이드로 데이터를 전달하는 과정에 대한 질문이 있습니다. 여기서도 msw와 비슷한 도구를 사용한 동적 데이터 생성과 캐싱의 측면이 다루어져 있으니 참고할 수 있을 것입니다.

동적으로 생성된 데이터와 캐시된 데이터 사이의 불일치 문제를 해결하기 위해선, 서버 사이드와 클라이언트 사이드에서 동일한 초기 데이터를 사용하도록 구성하는 것이 중요합니다. msw와 faker를 사용하여 데이터를 생성하는 경우, 서버 사이드 렌더링 환경에서 데이터 생성 로직을 동일하게 적용하거나 초기 데이터를 공유하는 방식을 고려해 볼 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.

현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏

추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

홍홍님의 프로필 이미지
홍홍

작성한 질문수

질문하기