[Trend-Now] Next.js SSR에서 TanStack Query 적용하기

강수영·2025년 8월 23일
0

1. 문제 상황

게시판 목록 데이터를 keepPreviousData 기능을 활용하기 위해 TanStack Query로 불러오고 있습니다.

하지만 현재는 TanStack Query가 CSR로만 동작하기 때문에, 첫 로딩 시 데이터가 없어 화면이 잠깐 깜빡이는 문제가 발생했습니다.

2. 해결: SSR로 깜빡임 없애기

⚒️ 초기 셋팅 하기


// query-provider.tsx
function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  })
}

let browserQueryClient: QueryClient | undefined = undefined

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

// query-provider.tsx
'use client'

import { getQueryClient } from './queryClient';
import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

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

서버는 매 요청마다 새로 만들고, 브라우저는 전역에 한 번 만들어 재사용합니다. SSR 직후 불필요한 재요청을 막기 위해 staleTime을 60초로 둡니다.

❓ 왜 서버에서 매번 새 QueryClient 생성할까?

서버는 한 번에 여러 사람의 요청을 처리합니다.

만약 서버에서 만든 하나의 QueryClient를 계속 재사용한다면, 사용자 A가 불러온 데이터가 그대로 남아 있다가 사용자 B에게도 섞여서 보일 수 있습니다.

예를 들어,

  • A가 “게시판 1페이지”를 보고 있는데,
  • B가 “게시판 2페이지”를 열면, B 화면에 A의 데이터가 같이 섞여서 내려갈 수도 있습니다.

따라서 서버에서는 요청마다 새로운 QueryClient를 만들어서 사용자별로 완전히 분리합니다.

❓ 왜 QueryClient를 전역으로 저장할까?

브라우저에서 QueryClient를 계속 새로 만들면 캐시가 초기화되어 매번 데이터를 다시 불러오게 됩니다.

따라서 한 번 만든 인스턴스를 전역 변수에 저장해두고 계속 재사용합니다.

❓ 왜 staleTime을 0보다 크게 줄까?

기본값 staleTime: 0이면 SSR로 받은 데이터도 즉시 만료로 간주되어 하이드레이션 직후 재요청합니다.

따라서 staleTime을 60초 정도로 설정해 하이드레이션 이후에는 불필요한 재요청이 일어나지 않도록 합니다.


📌 데이터 Prefetch & Hydration 과정

// page.tsx
export default async function Page({ params }: { params: Promise<{ boardId: string }> }) {
  const queryClient = getQueryClient();
  const { boardId } = await params;

  await queryClient.prefetchQuery({
    queryKey: ['posts', Number(boardId), 1],
    queryFn: () => axiosPosts(Number(boardId), 1, BOARD_PAGE_SIZE),
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Board boardId={Number(boardId)} />
    </HydrationBoundary>
  );
}
  1. 서버에서 QueryClient를 생성한다.
  2. queryClient.prefetchQuery로 필요한 데이터를 미리 불러와 캐시에 담는다.
  3. dehydrate(queryClient)로 직렬화된 캐시 상태를 만든다.
  4. HydrationBoundary로 감싸 클라이언트 컴포넌트에 전달한다.
  5. 클라이언트에서는 이미 캐시가 채워져 있으므로, 즉시 데이터를 사용할 수 있다.

3. 결과

SSR을 적용한 이후에는 첫 화면 로딩 시 깜빡임 현상이 사라졌습니다.

그 결과, 사용자 경험(UX)이 한층 향상되었습니다.

출처

profile
프론트엔드 개발자

0개의 댓글