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

dev님의 프로필 이미지
dev

작성한 질문수

손에 익는 Next.js - 공식 문서 훑어보기

안녕하세요 generateStaticParams 관해서 질문

작성

·

111

0

image.png
// [country]/layout.tsx

const SUPPORTED_LANGS = ['kr', 'us']; 

interface LangMap {
  [key: string]: string;
}

const LANG_MAP: LangMap = {
  kr: 'ko',
  us: 'en'
};

export function generateStaticParams() {
  return SUPPORTED_LANGS.map(country => ({country}));
}

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { country: string };
}) {


  return (
    <html lang={LANG_MAP[params.country]}>
      <body>
        <TanstackQueryProvider>
         <div className='container'>
          <BannerWrap/>
          <Header/>
          <main>{children}</main>
          <Footer/>
         </div>
        </TanstackQueryProvider>
        </body>
    </html>
  );
}
import Banner from "@/component/Banner";
import { getQueryClient } from "@/component/TanstackQueryOption";
import { fetchBanner } from "@/fetch/getReviews";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

export default function BannerWrap () {
  const queryClient = getQueryClient();

  queryClient.prefetchQuery({
    queryKey:['banners'],
    queryFn: fetchBanner,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Banner />
    </HydrationBoundary>
  )
}

 

image.png


빌드 후 next start로 테스트 중 의문점이 들어서 질문드립니다!
generateStaticParams 와 fetch의 revalidate 10초, tanstackQuery staleTime 10초 로 설정후
RootLayout 에서 BannerWrap에 prefetchQuery와 HydrationBoundary 를 통하여 data를 prefetch하고있는데 이게 layout이 전역에서 실행되서 그런지 생성된 페이지 개수만큼 요청을 날리는데
next에서는 같은 요청이 중복되지 않도록 요청을 캐시하고 중복 요청을 제거 한다고 배웠는데
이렇게 중복으로 요청하는 이유는 뭘까요?

답변 2

0

하조은님의 프로필 이미지
하조은
지식공유자

안녕하세요! 회신이 늦었습니다.
고민 많이 하고 계실텐데 답변이 늦어서 죄송합니다.

 

가능하다면 전체 코드를 알려주시면 확인이 조금 더 쉬울 것 같습니다. 짐작해보건데 빌드 시점에 prefetchQuery가 동작하면서 로그를 남기고 있는 게 아닐까 싶어요.

 

말씀주신 것처럼 Next.js의 캐시 있어서 중복 요청을 제거합니다. 이는 fetch 함수를 이용한 API 호출인 경우입니다. fetch 이전에 fetchBanner 내부에서 콘솔을 남기고 있는 것이라면 prefetchQuery 는 매번 호출될 수 있을 것 같습니다.

 

기다려주셨는데 명쾌한 답변을 드리지 못한 점 양해 부탁드립니다! 코드 저장소를 알려주시면 살펴보고 빠르게 답변 드릴게요!

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

안녕하세요 저도 확인이 늦었네요ㅠㅠ
저장소에 올렸습니다! 다시 한번 확인 해봐도 빌드 시점 콘솔도 페이지 개수대로 출력되고,빌드 후 런타임 시점에 콘솔이 revalidate 시간에 맞춰서 페이지가 재갱신되면서 api 호출이 페이지 개수만큼 호출되는거같은데..이러면 나중에 페이지 100개 200개가 되엇을때 문제가 일어날거같아서 꼭 해결하고싶은데 감이 안잡히네요ㅠㅠ

하조은님의 프로필 이미지
하조은
지식공유자

안녕하세요!
코드를 살펴봤어요. Express 서버도 함께 구현해주셨더라고요! 👍

 

Express 서버를 띄우고 Next.js를 빌드 해보니 말씀주신 것처럼 Next.js에 여러차례 로그가 남는 걸 확인했어요. 하지만 Express에 들어온 요청을 보면 API당 1회씩만 요청이 들어온 걸 보실 수 있을거에요.

 

이는 Next.js의 캐시가 fetch API에서 일어나기 때문이에요. 로그를 남기는 logRequestInfofetch보다 이전에 로그를 남기는데 이는 fetch 함수의 호출 여부를 의미할 순 있지만실제 API 호출 여부를 판단하는 로그가 될 수 없어요.

 

실제 API가 호출되어 네트워크를 탔는지 확인하기 위해선 Express 서버의 로그를 보시는 게 정확해요. Express에 들어온 요청을 보면 API당 1회씩만 요청이 들어오고 있어서 Next.js 서버 입장에선 정상적으로 캐시된 데이터를 활용하고 있는 걸로 보여요.

 

혹시나 제가 문제를 잘못 이해했다면 질문 다시 남겨주세요! 해결하실 때까지 도와드릴게요

 

추가로 캐시 정책에 이해를 돕는 자료를 남겨둘게요!

캐시 정책 이미지 / 공식 문서

image.png

 

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

빠른 답변 너무 감사합니다!
저도 Express 들어온 요청을 확인해보았는데

image.png


드래그 된 로그가 처음 요청 로그이며 그 후에 새로고침 시 호출하는 로그입니다!
근데 지금 현재 보면 똑같이 nextjs 만큼 찍히는건 아닌거같지만 Express 로그에도 중복해서 요청하고있어서요! 제가 보았을때는 현재 경로가 kr이여서 kr/, kr/products, kr/category 이렇게 3개의 생성된 페이지에서 중복된 api를 호출하고있는거같은데 아닌가요?🥲

그리고 간헐적으로 또 똑같은 api의 로그가 3개가 찍히는게 아닌

image.png

3번씩 호출했다가 2번씩 호출하는 경우도있고

image.png


아래처럼 같은 api를 6번 호출하는 경우도있네요ㅠ..
왜 이런 증상이 일어나는지 이해가 안가서 캐싱의 대해서 활용하기가 더 힘든거같아요..
무슨 경우일까요..?

하조은님의 프로필 이미지
하조은
지식공유자

확실히 헷갈리실 것 같네요 ㅠㅠ

 

혹시 새로고침 이후에 다시 요청이 되는건 revalidate 옵션을 10으로 주셨기 때문이 아닐까요? 위에 올려드렸던 이미지를 통해 설명하자면 10초 이내라면 캐시를 활용하지만 그 이후의 새로고침이라면 다시 API를 요청해서 데이터를 가져올 거에요.

 

호출 횟수가 4회로 일관되지 못한 건 제가 정확하게 환경을 보지 못해서 판단하기 어렵네요. 혹시 탭 여러 개에서 서비스를 띄워두고 계신 건 아닌지 확인해주실 수 있을까요?

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

앗 제가 설명을 제대로 못한거같네요 ㅠㅠ..
우선 기존에 보여드렸던 저장소에서,

스크린샷 2024-11-04 오후 4.09.19.png

아래와 같이 generateStaticParams를 삭제하였습니다.

image.png


revalidate 를 10으로 준건 말씀하신대로 새로고침 이후에 다시 요청되는걸 빨리 확인하기 위해 일부로 적은 시간으로 줘서 확인하고있었습니다! 그래서 10 초 이내에는 캐시된 데이터를 활용하지만 새로고침 이후에는 다시 API를 요청하는게 원하는 상황은 맞았지만,
빌드 후 서버에 예상되는 호출은 이렇게 4가지였는데
1. Received KR banners request
2. Received KR menu request
3. Received US banners request
4. Received US menu request

image.png


지금은 위에 스크린샷처럼 빌드 후 첫 서버에 요청하는 호출은
Received KR banners request 를 2번
Received KR menu request를 2번
Received US banners request를 2번
Received US menu request를 2번
하고 있습니다.

그래서 이거로 예상해보았을때 빌드시점에는 총 4개(kr 2개, us 2개)의
아래 페이지가 생성되니깐
app/[country]/page.tsx = /kr
app/[country]/products/page.tsx = /kr/products


app/[country]/page.tsx = /us
app/[country]/products/page.tsx = /us/products


페이지의 개수에 맞게 각각 KR , US 별로 2번 요청을 하고있다고 인지하였습니다.
페이지 재갱신 시간을 10초로 주었기때문에 10초 후의 새로고침때 새롭게 페이지를 갱신하지만, 현재의 [country]주소는 kr 에서 새로고침하였기때문에 API 호출은 아래와 같이

image.png


KR에 관한 API 서버요청이 KR의 페이지수 만큼만 요청을하고있는 걸로도 확인이 되었는데, 원하는 동작은 레이아웃에서 아래에서 처럼 사용하고있지만, 재갱신 시간이 지난 후 새로고침 시 페이지의 개수에 상관없이 한번만 호출하기를 원하는 상태입니다.
레이아웃의 하위 요소로 컴포넌트들이 들어가있는데, 그 레이아웃의 children으로 페이지들이 들어가니 이렇게 여러번 호출이 일어나는 문제로 생각이 되는데 이런걸 해결할수없다면 페이지가 500개인 사이트에서 재갱신 시간 이후에는 500개의 API 요청을 할거같은데
ISR로 구현하는 장점이 있는지 잘이해가 안가기도합니다!

image.pngimage.pngimage.pngimage.pngimage.png


말이 길었는데 결론은 재갱신 시간이 지난 후 새로고침 시 페이지의 개수에 상관없이 한번만 호출하기를 원하는데 가능할지..? 입니다!
우선 탭에서는 1개만 띄워놓고 테스트 진행하였습니다!

image.png





하조은님의 프로필 이미지
하조은
지식공유자

안녕하세요. 빠르게 답변드리고 싶어서 조금 알아봤는데, 저도 정확하게 모르는 이슈인 것 같습니다. 짐작하건데 TanStackQuery와의 조합에 문제가 있는 것 같아요.

 

TanStackQuery 문서에서 가이드하는 방법대로 사용하신 것 같은데 저도 그 이상의 방법을 알진 못하는 상황입니다.

 

Next.js의 Fetch API의 캐시 기능을 적극적으로 활용하시려면 TanStackQuery를 배제해보시는 건 어떨까요?

주신 코드에서 TanStackQuery를 제거하고 Fetch 만으로 서버 컴포넌트와 클라이언트 컴포넌트를 적극 활용해보았을 땐 캐시가 정상적으로 동작하는 걸 확인했습니다.

 

빠르게 답변 드리려다보니 이정도로 답변 드립니다. 조금 더 알아보고 원인을 알게 되면 답변 남기겠습니다!

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

답변 감사합니다! 저도 계속 해결해보고 방법알게된다면 여기에 답변 남겨놓겠습니다!

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

export function generateStaticParams() {
  return SUPPORTED_LANGS.map(country => ({country}));
}

fetch만 사용하거나, tanstackQuery만 사용해도 해당 코드만 삭제하면 정적빌드가 되지않아서 정상적으로 1번만 호출하는데
"코드에서 TanStackQuery를 제거하고 Fetch 만으로 서버 컴포넌트와 클라이언트 컴포넌트를 적극 활용해보았을 땐 캐시가 정상적으로 동작하는 걸 확인했습니다."
이 부분이 혹시 어떻게 코드를 수정하신건지 공유 한번 해주실수있을까요?

하조은님의 프로필 이미지
하조은
지식공유자

저도 TanStackQuery 제거와 함께 ISR도 제거했었어요~ 동일하게 해결하신 것 같아요!

 

이전에 작업했던 노트북과 다른 환경에서 답변 남기느라 제대로 답변 드리지 못했네요. 방금 로컬에서 다시 확인해보니 generateStaticParams는 유지한 상태로 작업했어요! 해당 내용 PR로 올려드릴게요

하조은님의 프로필 이미지
하조은
지식공유자

PR 남겨뒀어요! 원하시는 방향으로 해결됐는지 모르겠네요. 확인 부탁드려요!

https://github.com/jy0813/cache-test/pull/1

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

안녕하세요! 직접 확인해주시고 감사합니다 🥺
현재 올려주신 코드 pull 받은 후에 다시 한번 테스트 해보았는데,
로그 확인 결과 아직도 아래와 같이 나오네요..
캐시도 삭제해보고 다 해본건데 위에 올렸던 아래 이미지 같이 중복해서 호출이 일어나고있습니다..ㅎ generateStaticParams 를 포기해야 하는 방향으로 가야하나싶네요...

image.png


스크린샷 2024-11-06 오전 11.06.06.png


하조은님의 프로필 이미지
하조은
지식공유자

네, ISR은 아무래도 페이지 단위로 재생성을 하기 때문에 요구사항과 맞지 않을 수 있을 거 같네요 ㅠㅠ 제 기대와 다르게 동작해서 바로 적절한 답을 드리지 못했던 거 같아 아쉽네요 ㅠ

 

하지만 함께 고민하고 풀어갈 수 있어서 재밌었어요!

 다음에 다른 문제를 만나면 그때 또 질문 남겨주세요. 같이 고민해봐요!

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

아닙니다! 답변주셔서 감사합니다ㅎㅎㅎ

0

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

dev님, Next.js에서는 일반적으로 데이터 요청의 중복을 줄이기 위해 요청을 캐시하는 메커니즘을 제공합니다. 그러나 현재 언급하신 상황에서는 여러 이유로 인해 중복 요청이 발생할 수 있습니다:

  1. 동적 라우팅 연결: 코드에서 generateStaticParams 함수로 여러 국가에 대한 페이지를 생성하고 있습니다. RootLayout이 각 페이지별로 렌더링되기 때문에, 각기 다른 페이지가 각각의 데이터 요청을 발생시킬 수 있습니다.

  2. 탄스택 쿼리(Tanstack Query) 설정: 사용하신 staleTimerevalidate 설정은 데이터의 신선도에 영향을 미치며, 각 요청이 필요할 때마다 새로 만들어진 객체에 의해 다르게 캐시될 수 있습니다.

  3. 서버 사이드 렌더링의 영향: prefetchQuery에 의해 서버에서 데이터를 미리 가져오는 경우에도 특정 상황에서는 동일한 요청이 클라이언트로 넘겨지면서 중복될 수 있습니다.

  4. Hydration의 영향: HydrationBoundary를 사용하여 SSR(서버 사이드 렌더링)된 데이터를 클라이언트로 전달하지만, 그 과정에서 특정 쿼리들이 예상하지 못했던 시간에 재요청될 여지가 있습니다.

이와 같은 이슈를 해결하기 위해, 탄스택 쿼리의 캐시 설정을 철저히 설정하고, SSR과 관련된 설정을 조정하여 중복 요청을 최소화할 필요가 있습니다. 또한, 필요할 경우에 레이지 로딩(lazy loading)이나 데이터 패칭 로직의 최적화를 고려해 볼 수도 있습니다.

더 알고 싶으시면 Next.js 공식 문서Tanstack Query 공식 문서에서 관련 내용을 확인하는 것을 추천드립니다.

dev님의 프로필 이미지
dev

작성한 질문수

질문하기