시작하며...
이번 프로젝트에서 Next.js App Router 를 사용하면서 TanStack Query 를 사용하기로 했다.
그런데 Next.js 에서는 데이터 패칭을 할 땐 Server Sider Rendering 을 사용하는데 이걸 TanStack query 와 함께 사용하는 방법이 없을까? 하고 찾아보다가 공식문서에서 이에 관한 내용을 발견하였다.
TanStack Query 는 SSR 과 CSR 을 유연하게 통합할 수 있다고 한다.
그래서 Next.js 의 SSR 과 TanStack Query 의 CSR 을 통합하여 프로젝트에 적용한 내용에 대해 작성하게 되었다.
세팅하기
Next.js 에서 TanStack Query 를 사용하기 위해서 세팅을 해줘야 한다.
App Provider
App Provider 라는 새로운 파일을 만들어, 공식문서에 쓰여있는 대로 세팅을 하였다.
queryClient
queryClient 에서 기본 옵션들을 원하는 대로 설정한다.
여기서는 queryClient 를 설정하고 Next.js 에 제공하여 사용할 수 있게 해 준다.
그래서 App Provider 로 감싸면 그 하위에 있는 컴포넌트에서 TanStack Query 의 훅을 사용할 수 있게 된다.
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { PropsWithChildren, useState } from "react";
const AppProvider = ({ children }: PropsWithChildren) => {
const [client] = useState(
new QueryClient({
defaultOptions: {
queries: {
retry: false, // 실패하면 재시도 X
staleTime: 60000, // 캐시 유지 시간 1분
},
},
}),
);
return (
<QueryClientProvider client={client}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
};
export default AppProvider;
layout.tsx
app 디렉토리에 있는 루트 layout.tsx
파일에서 위에서 만든 App Provider 를 import 하여 감싸준다.
const RootLayout: React.FC<Readonly<{ children: React.ReactNode }>> = ({
children,
}) => (
<html lang="ko" className="dark">
<body
className={`${inter.className} min-w-330 dark:bg-background-primary dark:text-text-primary`}
>
<AppProvider>
<ToastContainer />
<NavBar />
<main className="px-16 ">{children}</main>
</AppProvider>
</body>
</html>
);
export default RootLayout;
TanStack Query + SSR
Next.js 의 SSR 을 사용하여 데이터를 초기 상태로 제공하고, TanStack Query 를 사용하여 클라이언트 측에서 데이터를 사용하는 방식에 대해 본격적으로 알아볼 것이다.
1. 컴포넌트 정의
먼저 새로운 페이지 디렉토리를 만들고 하위에 page.tsx
파일을 만들어 서버 컴포넌트를 생성한다.
그리고 queryClient 객체를 생성하고 필요한 값들을 가져온다.
const BoardPage = async ({ params }: { params: { boardId: number } }) => {
const queryClient = new QueryClient();
const { boardId } = params;
const userId = cookies().get("userId")?.value;
2. SSR
게시물 상세 데이터와 댓글 데이터 이렇게 2개 요청해야 하므로 Promise.all 을 사용하여 비동기로 데이터를 가져왔다.
여기서는 fetchQuery 와 prefetchInfiniteQuery 를 사용하였다.
fetch vs prefetch
이 둘은 유사하지만 약간의 차이점이 있다.
- fetch: 쿼리를 즉시 실행하고, 실패하면 에러를 던진다.
- SSR 에서 데이터를 미리 가져와 결과를 바로 사용하는 경우 적합
- prefecth: 쿼리를 미리 가져오지만, 반환값을 직접 사용하지 않고 실패해도 에러를 던지지 않는다.
- 특정 데이터를 미리 가져와 나중에 사용하는 경우 적합
그래서 게시물의 상세정보는 fetch 를 사용하였고, 댓글은 prefetch 를 사용하였다.
await Promise.all([
queryClient.fetchQuery({
queryKey: ["board", boardId],
queryFn: () => getBoardDetailData(boardId),
}),
queryClient.prefetchInfiniteQuery({
queryKey: ["board-comment", boardId],
queryFn: ({ pageParam }) => getBoardComment(boardId, pageParam),
initialPageParam: 0,
}),
]);
3. Hydration
Hydration 을 통해 서버 상태를 전달해야 한다.
HydrationBoundary 는 dehydrate(queryClient) 을 통해 서버에서 가져온 데이터를 클라이언트로 전달한다.
그러면 클라이언트에서는 데이터를 다시 가져올 필요 없이, 서버에서 가져온 데이터를 그대로 사용할 수 있게 된다.
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<BoardDetail userId={Number(userId)} boardId={boardId} />
<CommentList boardId={boardId} />
</HydrationBoundary>
);
4. 클라이언트에서 useQuery
BoardDetail 클라이언트 컴포넌트를 만들어, useQuery 훅을 사용하여 데이터를 가져온다.
이때 서버에서 미리 가져온 데이터와 동일한 queryKey 를 가지고 있어야 동일한 데이터를 재사용할 수 있게 된다.
const { data: boardData, error } = useQuery({
queryKey: ["board", boardId],
queryFn: () => getBoardDetailData(boardId),
});
그리고 예외 처리도 하였다.
if (!boardData) {
return redirect("/not-found");
}
if (error) {
return redirect("/error");
}
그러면 이제 Next.js 에서 TanStack Query 를 사용해서 Server Sider Rendering 을 할 수 있게 되었다!
결과
게시판 목록 페이지에서 상세 게시물이 미리 가져와지는 것을 확인할 수 있고, 게시물 상세 페이지에 들어가면 바로 렌더링 된다.
마치며...
Next.js 의 SSR 과 TanStack Query 를 통합하여 데이터 패칭을 효율적으로 사용하는 방법에 대해 알아보았다.
SSR 과 TanStack Query 두 개의 장점을 모두 사용하여 초기 데이터 로딩 속도를 개선하고, 클라이언트에서 데이터 재사용을 최적화할 수 있게 되었다.
사실 처음엔 Next.js 의 SSR 을 사용하면 되니깐 TanStack Query 를 사용할 일이 별로 없을 것이라 생각했다.
그러나 게시물 댓글에서 useInfiniteQuery 를 사용하고 싶어 SSR + TanStack Query 방식을 사용해 보니깐, 실제로 너무 유용해서 대부분 페이지에서도 해당 방식을 사용하였다.
특히 데이터가 바뀌는 곳이면 쿼리 초기화를 하거나 setQueryData 를 사용하여 바로 데이터를 변경하여 매우 유용했다 😊
'💜 리액트 > Next.js' 카테고리의 다른 글
[Next.js] API Routes로 DynamoDB 정렬하기 (feat. AWS Amplify) (1) | 2024.11.20 |
---|---|
[Next.js] 채널톡 연동하기 구현 (feat. app router, typescript) (1) | 2024.08.28 |
[Next.js] Server Sider Rendering 특정 사용자 접근 제한 (feat. App Router) (0) | 2024.08.11 |
[Next.js] 메타태그, 오픈그래프 컴포넌트 SEO 향상 (feat. Page Router) (0) | 2024.07.12 |
[Next.js] Framer Motion 화면 전환 애니메이션 적용 (feat. Page Router) (7) | 2024.07.10 |
FE 개발자가 되고 싶은 짱잼이
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!